5 Commits
1.2.2 ... main

Author SHA1 Message Date
6c64b4cee8 Now compiles with warning flags for C++
Some checks failed
Test And Build / Lint (push) Successful in 4s
Test And Build / Build (push) Failing after 6s
2025-12-23 13:48:18 +01:00
df09b1949e Updated turboPmac version which applies compile flags to the C++ code as well 2025-12-23 13:36:21 +01:00
49c8ff5497 Fixed README formatting
Some checks failed
Test And Build / Lint (push) Failing after 4s
Test And Build / Build (push) Successful in 14s
2025-12-23 12:14:39 +01:00
761c691a86 Updated sinqMotor to fix segfault 2025-12-23 12:08:33 +01:00
aa5f2f0315 Updated turboPmac and removed C++ only flag
Some checks failed
Test And Build / Lint (push) Failing after 4s
Test And Build / Build (push) Successful in 16s
2025-08-22 15:35:49 +02:00
10 changed files with 173 additions and 53 deletions

View File

@@ -42,4 +42,5 @@ DBDS += turboPmac/sinqMotor/src/sinqMotor.dbd
DBDS += turboPmac/src/turboPmac.dbd
DBDS += src/detectorTower.dbd
USR_CFLAGS += -Wall -Wextra -Weffc++ -Wunused-result -Wextra -Werror -fvisibility=hidden # -Wpedantic // Does not work because EPICS macros trigger warnings
USR_CFLAGS += -Wall -Wextra -Wunused-result -Wextra -Werror # -Wpedantic // Does not work because EPICS macros trigger warnings
USR_CXXFLAGS += -Wall -Wextra -Wunused-result -Werror

138
README.md
View File

@@ -1,62 +1,107 @@
# detectorTower
## <span style="color:red">Please read the documentation of sinqMotor first: https://git.psi.ch/sinq-epics-modules/sinqmotor</span>
## <span style="color:red">Please read the documentation of sinqMotor first:https://git.psi.ch/sinq-epics-modules/sinqmotor</span>
## Overview
![Coordinate systems](images/CoordinateSystems.svg)
This is a driver for the detector tower which is based on the Turbo PMAC driver (https://gitea.psi.ch/lin-epics-modules/turboPmac). It consists of the following four objects:
- `detectorTowerController`: This is an expanded variant of `turboPmacController` provided by the Turbo PMAC library linked above.It is needed to operate a `detectorTowerAngleAxis`, but it can also be used to operate a "normal" `turboPmacAxis`.
- `detectorTowerAngleAxis`: This is a virtual axis which controls multiple physical motors ($x$ and $z$) in order to provide a combined movement. Moving it results in a rotation of the entire beam around the support axis position ($\alpha$).
- `detectorTowerLiftAxis`: This is a virtual axis which controls multiple physical motors in order to provide a combined movement. Moving it results in a vertical lift ($z_{lift}$).
- `detectorTowerSupportAxis`: This is an axis at the rotation center of the beam which is part of the combined movements. Its origin can be shifted manually for small adjustments, resulting in a corresponding movement. Other than that, it is not meant to move on its own, hence setting a new value to the `VAL` field of the motor record won't cause it to move.
This is a driver for the detector tower which is based on the Turbo PMAC driver
(https://gitea.psi.ch/lin-epics-modules/turboPmac). It consists of the following
four objects:
- `detectorTowerController`: This is an extended variant of `turboPmacController`
provided by the Turbo PMAC library linked above.It is needed to operate a
`detectorTowerAngleAxis`, but it can also be used to operate a "normal" `turboPmacAxis`.
- `detectorTowerAngleAxis`: This is a virtual axis which controls multiple
physical motors ($x$ and $z$) in order to provide a combined movement. Moving it
results in a rotation of the entire beam around the support axis position ($\alpha$).
- `detectorTowerLiftAxis`: This is a virtual axis which controls multiple
physical motors in order to provide a combined movement. Moving it results in a
vertical lift ($z_{lift}$).
- `detectorTowerSupportAxis`: This is an axis at the rotation center of the beam
which is part of the combined movements. Its origin can be shifted manually for
small adjustments, resulting in a corresponding movement. Other than that, it is
not meant to move on its own, hence setting a new value to the `VAL` field of
the motor record won't cause it to move.
## User guide
The centerpiece of this driver is the `detectorTowerAngleAxis`, which controls the angle of the detector flight tube. It is supported by two secondary axes, `detectorTowerLiftAxis` and `detectorTowerSupportAxis`. All three axes are created by a single IOC shell command `detectorTowerAxis` (see [Usage in IOC shell](#usage-in-ioc-shell)). All three axes use absolute encoders and therefore cannot perform a reference / home drive.
The centerpiece of this driver is the `detectorTowerAngleAxis`, which controls
the angle of the detector flight tube. It is supported by two secondary axes,
`detectorTowerLiftAxis` and `detectorTowerSupportAxis`. All three axes are
created by a single IOC shell command `detectorTowerAxis` (see
[Usage in IOC shell](#usage-in-ioc-shell)). All three axes use absolute encoders
and therefore cannot perform a reference / home drive.
The first two axes can be moved independently from each other or together as a combined movement by issuing both move orders within a certain time span. This time span can be configured via the IOC shell function `setDeferredMovementWait` and defaults to 100 ms. This allows the user to start a combined movement e.g. via caput:
The first two axes can be moved independently from each other or together as a
combined movement by issuing both move orders within a certain time span. This
time span can be configured via the IOC shell function `setDeferredMovementWait`
and defaults to 100 ms. This allows the user to start a combined movement e.g.
via caput:
```
```bash
caput $(ControllerPV):angle 2 & $(ControllerPV):caput lift 10
```
which moves the angle to 2 degree and shifts the entire beam vertically by 10 mm. When using the axis from NICOS, using the `maw` or `move` command with multiple devices has the same effect:
which moves the angle to 2 degree and shifts the entire beam vertically by 10 mm.
When using the axis from NICOS, using the `maw` or `move` command with multiple
devices has the same effect:
```
```bash
move("angle", 2, "lift", 10)
```
If one axis is already moving, no new move command can be issued until the movement is finished. The `detectorTowerSupportAxis` cannot be moved directly.
If one axis is already moving, no new move command can be issued until the
movement is finished. The `detectorTowerSupportAxis` cannot be moved directly.
It is possible to shift the origin of all three axes (and therefore also moving the `detectorTowerSupportAxis`) for small adjustments. The current origin position in the axis unit (degree for `detectorTowerAngleAxis`, mm for `detectorTowerLiftAxis` and encoder steps for `detectorTowerSupportAxis`) can be read from the PV `$(INSTR)$(M):Origin`. The origin can be shifted by the amount of axis units written to `$(INSTR)$(M):AdjustOrigin`. Since this is a relative movement, writing "10" two times to `$(INSTR)$(M):AdjustOrigin` shifts the origin by 20 axis units. Therefore, the limits are only there to prevent too large shifts during a single write (since the origin can be shifted to any position by writing to `$(INSTR)$(M):AdjustOrigin` repeatedly).
It is possible to shift the origin of all three axes (and therefore also moving
the `detectorTowerSupportAxis`) for small adjustments. The current origin
position in the axis unit (degree for `detectorTowerAngleAxis`, mm for
`detectorTowerLiftAxis` and encoder steps for `detectorTowerSupportAxis`) can be
read from the PV `$(INSTR)$(M):Origin`. The origin can be shifted by the amount
of axis units written to `$(INSTR)$(M):AdjustOrigin`. Since this is a relative
movement, writing "10" two times to `$(INSTR)$(M):AdjustOrigin` shifts the
origin by 20 axis units. Therefore, the limits are only there to prevent too
large shifts during a single write (since the origin can be shifted to any
position by writing to `$(INSTR)$(M):AdjustOrigin` repeatedly).
The detector tower can be moved into a so-called "changer position" by writing "1" to the PV `$(INSTR)$(M):ChangeState`. This causes the entire tower to move vertically down. When it is in the changer position, the axes cannot be moved at all. In order to go back to the "working state" where the axes can be moved again, write "0" to `$(INSTR)$(M):ChangeState`. The current state of the axis can be read out from `$(INSTR)$(M):ChangingStateRBV`. In case starting a change movement succeeds, `$(INSTR)$(M):ChangeStateRBV` changes its value accordingly, otherwise it stays at its current value.
The detector tower can be moved into a so-called "changer position" by writing
"1" to the PV `$(INSTR)$(M):ChangeState`. This causes the entire tower to move
vertically down. When it is in the changer position, the axes cannot be moved at
all. In order to go back to the "working state" where the axes can be moved
again, write "0" to `$(INSTR)$(M):ChangeState`. The current state of the axis
can be read out from `$(INSTR)$(M):ChangingStateRBV`. In case starting a change
movement succeeds, `$(INSTR)$(M):ChangeStateRBV` changes its value accordingly,
otherwise it stays at its current value.
The utilities provided in the `utils` folder in `turboPmac/utils` work with this driver as well.
The utilities provided in the `utils` folder in `turboPmac/utils` work with this
driver as well.
### Usage in IOC shell
detectorTower exports the following IOC shell functions:
- `detectorTowerController`: Create a new controller object.
- `detectorTowerAxis`: Create a `detectorTowerAngleAxis`, a `detectorTowerLiftAxis` and a `detectorTowerSupportAxis` object and link them to each other
- `detectorTowerAxis`: Create a `detectorTowerAngleAxis`, a `detectorTowerLiftAxis`
and a `detectorTowerSupportAxis` object and link them to each other
The constructor function for `detectorTowerAxis` has the following syntax:
```
```bash
detectorTowerAngleAxis("$(NAME)",1, 2, 3)
```
with 1 being the axis number / index of the `detectorTowerAngleAxis`, 2 being the `detectorTowerLiftAxis` and 3 being the `detectorTowerSupportAxis`. These axes are parametrized in the same way as any "normal" axes via a substitution file (see corresponding section below).
with 1 being the axis number / index of the `detectorTowerAngleAxis`, 2 being
the `detectorTowerLiftAxis` and 3 being the `detectorTowerSupportAxis`. These
axes are parametrized in the same way as any "normal" axes via a substitution
file (see corresponding section below).
"Normal" `turboPmacAxis` may be used together with this controller:
```
```bash
# Define the name of the controller and the corresponding port
epicsEnvSet("NAME","mcu")
epicsEnvSet("ASYN_PORT","p$(NAME)")
# Create the TCP/IP socket used to talk with the controller. The socket can be adressed from within the IOC shell via the port name
# Create the TCP/IP socket used to talk with the controller. The socket can beadressed from within the IOC shell via the port name
drvAsynIPPortConfigure("$(ASYN_PORT)","172.28.101.24:1025")
# Create the controller object with the defined name and connect it to the socket via the port name.
@@ -91,13 +136,17 @@ dbLoadRecords("$(sinqMotor_DB)/asynRecord.db","P=$(INSTR)$(NAME),PORT=$(ASYN_POR
### Substitution file
From the perspective of EPICS, the main detector flight tube axis and the auxiliary axes are independent axes and therefore each axis needs its own parametrization in the substitution file. If additional axes are used in the axis, they are parametrized as usual:
```
From the perspective of EPICS, the main detector flight tube axis and the
auxiliary axes are independent axes and therefore each axis needs its own
parametrization in the substitution file. If additional axes are used in the
axis, they are parametrized as usual:
```bash
detectorTowerAngleAxis("$(NAME)",1, 2, 3);
turboPmacAxis("$(NAME)",4);
turboPmacAxis("$(NAME)",5);
```
```
```bash
file "$(SINQDBPATH)"
{
pattern
@@ -110,22 +159,53 @@ pattern
}
```
Note that the speed of the detector tower axes 1, 2 and 3 cannot be set. Setting `CANSETSPEED` to 1 does not change this behaviour.
Note that the speed of the detector tower axes 1, 2 and 3 cannot be set. Setting
`CANSETSPEED` to 1 does not change this behaviour.
## Developer guide
### Code architecture
The code is designed around the `detectorTowerAngleAxis`, which controls the angle of the beam guide and acts as a "master" axis which contains pointers to its attached `detectorTowerLiftAxis` and `detectorTowerSupportAxis`. All three axes are polled at once via the function `detectorTowerController::pollDetectorAxes`, which is called from the individual `poll` functions of the axes (the `doPoll` mechanism from `sinqMotor` is not used). To avoid polling the axes multiple times during one controller cycle, the function `detectorTowerController::pollDetectorAxes` is only executed for the axis with the smallest index. Since this axis is polled first, the other two axes are therefore already up-to-date when they execute their own poll function. If one of the axes is moving, all three axes are marked as moving. The same is true for errors.
The code is designed around the `detectorTowerAngleAxis`, which controls the
angle of the beam guide and acts as a "master" axis which contains pointers to
its attached `detectorTowerLiftAxis` and `detectorTowerSupportAxis`. All three
axes are polled at once via the function `detectorTowerController::pollDetectorAxes`,
which is called from the individual `poll` functions of the axes (the `doPoll`
mechanism from `sinqMotor` is not used). To avoid polling the axes multiple
times during one controller cycle, the function `detectorTowerController::pollDetectorAxes`
is only executed for the axis with the smallest index. Since this axis is polled
first, the other two axes are therefore already up-to-date when they execute
their own poll function. If one of the axes is moving, all three axes are marked
as moving. The same is true for errors.
In order to save on movement time, movement commands to auxiliary axes and the `detectorTowerAngleAxis` are collected and then send as a single resulting movement command to the MCU. In order to do so, a "collector" thread is running which checks if a movement request has been send to one of the axes. If that is the case, it waits for `detectorTowerAngleAxis->deferredMovementWait_` seconds and checks if commands for other axes are given as well. Then, it calls `detectorTowerAngleAxis::startCombinedMove` which combines all commands to a single request which is sent to the MCU. `detectorTowerAngleAxis->deferredMovementWait_` can be set with the IOC shell function `setDeferredMovementWait` and defaults to 100 ms.
In order to save on movement time, movement commands to auxiliary axes and the
`detectorTowerAngleAxis` are collected and then send as a single resulting
movement command to the MCU. In order to do so, a "collector" thread is running
which checks if a movement request has been send to one of the axes. If that is
the case, it waits for `detectorTowerAngleAxis->deferredMovementWait_` seconds
and checks if commands for other axes are given as well. Then, it calls
`detectorTowerAngleAxis::startCombinedMove` which combines all commands to a
single request which is sent to the MCU.
`detectorTowerAngleAxis->deferredMovementWait_` can be set with the IOC shell
function `setDeferredMovementWait` and defaults to 100 ms.
The `detectorTowerController` is a thin wrapper around a `turboPmacController` which overwrites the `readInt32`, `writeInt32` and `writeFloat64` methods in order to support the additional PVs defined in `db/detectorTower.db`. Any calls to these two methods not concerning the aforementioned PVs are directly forwarded to `turboPmacController::readInt32` / `turboPmacController::writeInt32`.
The `detectorTowerController` is a thin wrapper around a `turboPmacController`
which overwrites the `readInt32`, `writeInt32` and `writeFloat64` methods in
order to support the additional PVs defined in `db/detectorTower.db`. Any calls
to these two methods not concerning the aforementioned PVs are directly
forwarded to `turboPmacController::readInt32` / `turboPmacController::writeInt32`.
### Versioning
Please see the documentation for the module sinqMotor: https://git.psi.ch/sinq-epics-modules/sinqmotor/-/blob/main/README.md.
Please see the documentation for the module sinqMotor:
https://git.psi.ch/sinq-epics-modules/sinqmotor/-/blob/main/README.md.
### How to build it
This driver can be compiled and installed by running `make install` from the same directory where the Makefile is located. However, since it uses the git submodule turboPmac, make sure that the correct version of the submodule repository is checked out AND the change is commited (`git status` shows no non-committed changes). Please see the section "Usage as static dependency" in https://git.psi.ch/sinq-epics-modules/sinqmotor/-/blob/main/README.md for more details.
This driver can be compiled and installed by running `make install` from the
same directory where the Makefile is located. However, since it uses the git
submodule turboPmac, make sure that the correct version of the submodule
repository is checked out AND the change is commited (`git status` shows no
non-committed changes). Please see the section "Usage as static dependency" in
https://git.psi.ch/sinq-epics-modules/sinqmotor/-/blob/main/README.md for more
details.

View File

@@ -179,6 +179,10 @@ asynStatus detectorTowerAngleAxis::poll(bool *moving) {
}
asynStatus detectorTowerAngleAxis::doPoll(bool *moving) {
// Suppress unused variable warning
(void)moving;
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nThe doPoll method "
"of this axis type should not be reachable. This is a bug.\n",
@@ -187,9 +191,16 @@ asynStatus detectorTowerAngleAxis::doPoll(bool *moving) {
}
asynStatus detectorTowerAngleAxis::doMove(double position, int relative,
double min_velocity,
double max_velocity,
double minVelocity,
double maxVelocity,
double acceleration) {
// Suppress unused variable warning
(void)relative;
(void)minVelocity;
(void)maxVelocity;
(void)acceleration;
double motorRecResolution = 0.0;
getAxisParamChecked(this, motorRecResolution, &motorRecResolution);
@@ -276,6 +287,9 @@ asynStatus detectorTowerAngleAxis::stop(double acceleration) {
// =========================================================================
// Suppress unused variable warning
(void)acceleration;
status = pC_->writeRead(axisNo_, "P350=8", response, 0);
if (status != asynSuccess) {
@@ -540,8 +554,9 @@ static const iocshArg *const CreateAxisArgs[] = {
&CreateAxisArg2,
&CreateAxisArg3,
};
static const iocshFuncDef configDetectorTowerCreateAxis = {"detectorTowerAxis",
4, CreateAxisArgs};
static const iocshFuncDef configDetectorTowerCreateAxis = {
"detectorTowerAxis", 4, CreateAxisArgs,
"Create new instances of detectorTower axes."};
static void configDetectorTowerCreateAxisCallFunc(const iocshArgBuf *args) {
detectorTowerCreateAxis(args[0].sval, args[1].ival, args[2].ival,
args[3].ival);
@@ -595,7 +610,8 @@ static const iocshArg *const setDeferredMovementWaitArgs[] = {
&setDeferredMovementWaitArg0, &setDeferredMovementWaitArg1,
&setDeferredMovementWaitArg2};
static const iocshFuncDef setDeferredMovementWaitDef = {
"setDeferredMovementWait", 3, setDeferredMovementWaitArgs};
"setDeferredMovementWait", 3, setDeferredMovementWaitArgs,
"Set the wait time in seconds for the deferred movement."};
static void setDeferredMovementWaitCallFunc(const iocshArgBuf *args) {
setDeferredMovementWait(args[0].sval, args[1].ival, args[2].dval);

View File

@@ -56,13 +56,13 @@ class HIDDEN detectorTowerAngleAxis : public turboPmacAxis {
*
* @param position
* @param relative
* @param min_velocity
* @param max_velocity
* @param minVelocity
* @param maxVelocity
* @param acceleration
* @return asynStatus
*/
asynStatus doMove(double position, int relative, double min_velocity,
double max_velocity, double acceleration);
asynStatus doMove(double position, int relative, double minVelocity,
double maxVelocity, double acceleration);
/**
* @brief Start a movement to the target positions of this axis and the

View File

@@ -26,7 +26,8 @@ detectorTowerController::detectorTowerController(
const char *portName, const char *ipPortConfigName, int numAxes,
double movingPollPeriod, double idlePollPeriod, double comTimeout)
: turboPmacController(portName, ipPortConfigName, numAxes, movingPollPeriod,
idlePollPeriod, NUM_detectorTower_DRIVER_PARAMS)
idlePollPeriod, comTimeout,
NUM_detectorTower_DRIVER_PARAMS)
{
@@ -1005,7 +1006,8 @@ static const iocshArg *const CreateControllerArgs[] = {
&CreateControllerArg0, &CreateControllerArg1, &CreateControllerArg2,
&CreateControllerArg3, &CreateControllerArg4, &CreateControllerArg5};
static const iocshFuncDef configDetectorTowerCreateController = {
"detectorTowerController", 6, CreateControllerArgs};
"detectorTowerController", 6, CreateControllerArgs,
"Create a new instance of a detector tower controller."};
static void
configDetectorTowerCreateControllerCallFunc(const iocshArgBuf *args) {
detectorTowerCreateController(args[0].sval, args[1].sval, args[2].ival,

View File

@@ -72,7 +72,7 @@ detectorTowerLiftAxis::detectorTowerLiftAxis(detectorTowerController *pC,
angleAxis->liftAxis_ = this;
// Initialize the flag to false
waitingForStart_ = false;
waitingForStart_ = false;
}
detectorTowerLiftAxis::~detectorTowerLiftAxis(void) {
@@ -149,6 +149,10 @@ asynStatus detectorTowerLiftAxis::poll(bool *moving) {
}
asynStatus detectorTowerLiftAxis::doPoll(bool *moving) {
// Suppress unused variable warnings
(void)moving;
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nThe doPoll method "
"of this axis type should not be reachable. This is a bug.\n",
@@ -157,9 +161,14 @@ asynStatus detectorTowerLiftAxis::doPoll(bool *moving) {
}
asynStatus detectorTowerLiftAxis::doMove(double position, int relative,
double min_velocity,
double max_velocity,
double minVelocity, double maxVelocity,
double acceleration) {
// Suppress unused variable warnings
(void)relative;
(void)minVelocity;
(void)maxVelocity;
(void)acceleration;
double motorRecResolution = 0.0;
getAxisParamChecked(this, motorRecResolution, &motorRecResolution);

View File

@@ -52,13 +52,13 @@ class HIDDEN detectorTowerLiftAxis : public turboPmacAxis {
*
* @param position
* @param relative
* @param min_velocity
* @param max_velocity
* @param minVelocity
* @param maxVelocity
* @param acceleration
* @return asynStatus
*/
asynStatus doMove(double position, int relative, double min_velocity,
double max_velocity, double acceleration);
asynStatus doMove(double position, int relative, double minVelocity,
double maxVelocity, double acceleration);
/**
* @brief Calls the `reset` function of the associated angle axis.

View File

@@ -146,6 +146,10 @@ asynStatus detectorTowerSupportAxis::poll(bool *moving) {
}
asynStatus detectorTowerSupportAxis::doPoll(bool *moving) {
// Suppress unused variable warnings
(void)moving;
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nThe doPoll method "
"of this axis type should not be reachable. This is a bug.\n",

View File

@@ -56,13 +56,21 @@ class HIDDEN detectorTowerSupportAxis : public turboPmacAxis {
*
* @param position
* @param relative
* @param min_velocity
* @param max_velocity
* @param minVelocity
* @param maxVelocity
* @param acceleration
* @return asynStatus
*/
asynStatus doMove(double position, int relative, double min_velocity,
double max_velocity, double acceleration) {
asynStatus doMove(double position, int relative, double minVelocity,
double maxVelocity, double acceleration) {
// Suppress unused variable warnings
(void)position;
(void)relative;
(void)minVelocity;
(void)maxVelocity;
(void)acceleration;
return asynSuccess;
}