Compare commits

..

38 Commits
1.6.3 ... 1.2.2

Author SHA1 Message Date
f423002d23 Updated sinqMotor 2025-06-18 08:26:49 +02:00
79ec79fac1 Updated sinqMotor 2025-06-18 08:18:53 +02:00
1703542770 Use new sinqMotor version 2025-06-17 13:16:20 +02:00
c7d1dc4930 Added getAxisParam variant for char arrays 2025-06-17 10:25:02 +02:00
6fd3848f13 Fixed template for char arrays 2025-06-17 08:45:07 +02:00
56f08f3c76 Fixed template error in sinqMotor 2025-06-17 08:41:07 +02:00
168bfae983 Updated sinqMotor 2025-06-17 08:34:07 +02:00
0e29750d13 Updated sinqMotor version 2025-06-16 16:18:06 +02:00
ba5b921aca Committed new sinqMotor version 2025-06-16 15:55:40 +02:00
1b810fb353 Adjusted dependency to use AxisParamChecked branch 2025-06-16 15:26:44 +02:00
4bc3388bc6 Added destructor for controller 2025-06-10 14:58:59 +02:00
c759156058 Added destructor for controller 2025-06-10 14:53:26 +02:00
eca513f3a0 Updated sinqMotor version to 1.1 2025-06-10 14:11:48 +02:00
26175290bf Added flag to disable reading the limits from the hardware 2025-06-10 13:53:39 +02:00
e316fcf67b Changed priority of IOC shell message 2025-06-06 11:20:50 +02:00
6cf00adb60 Ready for release 1.0 2025-05-23 12:27:31 +02:00
9710d442b8 Removed exit(-1) from init
When the initialization fails, the motor will now try again during each
poll.
2025-05-23 11:56:45 +02:00
8dd1dc4af2 Fixed poller bug in sinqMotor 2025-05-23 11:20:20 +02:00
a758db1211 Change to latest sinqMotor version 2025-05-23 10:11:27 +02:00
a11d10cb6c Updated sinqMotor to 0.15.2 2025-05-16 16:03:30 +02:00
ba353c4e5d Fixed remote git link in repo. 2025-05-16 15:58:17 +02:00
55b523ddaa Made some functions virtual so they can be overwritten in subclasses 2025-05-15 14:54:18 +02:00
75292a6a9c Fixed some merge bugs 2025-05-15 12:32:30 +02:00
53bbe2aae8 Merge branch 'static-dep' 2025-05-15 12:27:26 +02:00
1597dc34e0 Added default value for motorMessageText 2025-05-15 12:25:08 +02:00
dde7066f40 Use latest version of sinqMotor 2025-05-15 11:41:53 +02:00
b4d6447b32 Fixed serious bug in sinqMotor 2025-05-15 11:29:13 +02:00
2f83060ec1 Addes error message for failing to enable / disable within timeout 2025-05-14 16:26:55 +02:00
a3e3a79788 Adjusted usage of motorMessageText to act as an error message only. 2025-05-14 16:17:14 +02:00
2c5fdc7d0a Use new version of sinqMotor 2025-05-14 16:13:10 +02:00
7bf31ac256 Allow enabling / disabling the motor regardless of the status returned by the poll 2025-05-13 14:44:24 +02:00
47e72d65a9 Fixed small inaccuracy in the README 2025-05-12 17:04:09 +02:00
5298b5ef69 Fixed typo 2025-05-12 16:59:59 +02:00
29f23216ad Added some comments that this library now uses sinqMotor as static
dependency.
2025-05-12 16:43:57 +02:00
26bc3df876 Fixed bug where the motor could be in state -6 and the driver would
interpret this as "not moving"
2025-05-12 16:19:34 +02:00
87d3cbb3eb Specified new version for sinqMotor 2025-05-12 12:00:49 +02:00
66552d5ffc Pinned sinqMotor version 2025-05-12 11:58:56 +02:00
253f65b25b Added sinqMotor as a submodule for static linking 2025-05-12 08:52:10 +02:00
14 changed files with 327 additions and 476 deletions

View File

@@ -1,24 +0,0 @@
name: Test And Build
on: [push]
jobs:
Lint:
runs-on: linepics
steps:
- name: checkout repo
uses: actions/checkout@v4
- name: cppcheck
run: cppcheck --std=c++17 --addon=cert --addon=misc --error-exitcode=1 src/*.cpp
- name: formatting
run: clang-format --style=file --Werror --dry-run src/*.cpp
Build:
runs-on: linepics
steps:
- name: checkout repo
uses: actions/checkout@v4
with:
submodules: 'true'
- run: |
sed -i 's/ARCH_FILTER=.*/ARCH_FILTER=linux%/' Makefile
echo -e "\nIGNORE_SUBMODULES += sinqmotor" >> Makefile
make install

8
.gitignore vendored
View File

@@ -1,8 +0,0 @@
O.*
.cvsignore
.vscode
src/.vscode
utils/analyzeTcpDump/__pycache__
utils/analyzeTcpDump/demo.pcap.json
utils/analyzeTcpDump/demovenv
utils/analyzeTcpDump/venv

58
.gitlab-ci.yml Normal file
View File

@@ -0,0 +1,58 @@
default:
image: docker.psi.ch:5000/sinqdev/sinqepics:latest
stages:
- lint
- build
- test
cppcheck:
stage: lint
script:
- cppcheck --std=c++17 --addon=cert --addon=misc --error-exitcode=1 src/*.cpp
artifacts:
expire_in: 1 week
tags:
- sinq
formatting:
stage: lint
script:
- clang-format --style=file --Werror --dry-run src/*.cpp
artifacts:
expire_in: 1 week
tags:
- sinq
# clangtidy:
# stage: lint
# script:
# - curl https://docker.psi.ch:5000/v2/_catalog
# # - dnf update -y
# # - dnf install -y clang-tools-extra
# # - clang-tidy sinqEPICSApp/src/*.cpp sinqEPICSApp/src/*.c sinqEPICSApp/src/*.h -checks=cppcoreguidelines-*,cert-*
# # tags:
# # - sinq
build_module:
stage: build
script:
- export SINQMOTOR_VERSION="$(grep 'sinqMotor_VERSION=' Makefile | cut -d= -f2)"
- git clone --depth 1 --branch "${SINQMOTOR_VERSION}" https://gitlab-ci-token:${CI_JOB_TOKEN}@git.psi.ch/sinq-epics-modules/sinqmotor.git
- pushd sinqmotor
- sed -i 's/ARCH_FILTER=.*/ARCH_FILTER=linux%/' Makefile
- echo "LIBVERSION=${SINQMOTOR_VERSION}" >> Makefile
- make install
- popd
- sed -i 's/ARCH_FILTER=.*/ARCH_FILTER=linux%/' Makefile
- echo "LIBVERSION=${CI_COMMIT_TAG:-0.0.1}" >> Makefile
- make install
- cp -rT "/ioc/modules/turboPmac/$(ls -U /ioc/modules/turboPmac/ | head -1)" "./turboPmac-${CI_COMMIT_TAG:-$CI_COMMIT_SHORT_SHA}"
artifacts:
name: "turboPmac-${CI_COMMIT_TAG:-$CI_COMMIT_SHORT_SHA}"
paths:
- "turboPmac-${CI_COMMIT_TAG:-$CI_COMMIT_SHORT_SHA}/*"
expire_in: 1 week
when: always
tags:
- sinq

3
.gitmodules vendored
View File

@@ -1,3 +1,6 @@
[submodule "sinqmotor"]
path = sinqmotor
url = https://gitea.psi.ch/lin-epics-modules/sinqmotor
[submodule "sinqMotor"]
path = sinqMotor
url = https://gitea.psi.ch/lin-epics-modules/sinqMotor

View File

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

View File

@@ -1,29 +1,18 @@
# turboPmac
## <span style="color:red">Please read the documentation of sinqMotor first: https://gitea.psi.ch/lin-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
This is a driver for the Turbo PMAC motion controller with the SINQ
communication protocol. It is based on the sinqMotor shared library
(https://gitea.psi.ch/lin-epics-modules/sinqMotor). The header files contain
detailed documentation for all public functions. The headers themselves are
exported when building the library to allow other drivers to depend on this one.
This is a driver for the Turbo PMAC motion controller with the SINQ communication protocol. It is based on the sinqMotor shared library (https://gitea.psi.ch/lin-epics-modules/sinqMotor). The header files contain detailed documentation for all public functions. The headers themselves are exported when building the library to allow other drivers to depend on this one.
## User guide
This driver is a standard sinqMotor-derived which however uses a special low
level IP Port driver (`pmacAsynIPPortConfigure`) instead of the standard
`drvAsynIPPortConfigure`. For the general configuration, please see
https://gitea.psi.ch/lin-epics-modules/sinqMotor/src/branch/main/README.md.
This driver is a standard sinqMotor-derived which however uses a special low level IP Port driver (`pmacAsynIPPortConfigure`) instead of the standard `drvAsynIPPortConfigure`. For the general configuration, please see https://git.psi.ch/sinq-epics-modules/sinqmotor/-/blob/main/README.md.
The folder "utils" contains utility scripts for working with pmac motor
controllers. To read their manual, run the scripts without any arguments.
- writeRead.py: Allows sending commands to and receiving commands from a pmac
controller over an ethernet connection.
- analyzeTcpDump.py: Parse the TCP communication between an IOC and a MCU and
format it into a dictionary. "demo.py" shows how this data can be easily
visualized for analysis.
The folder "utils" contains utility scripts for working with pmac motor controllers. To read their manual, run the scripts without any arguments.
- writeRead.py: Allows sending commands to and receiving commands from a pmac controller over an ethernet connection.
- analyzeTcpDump.py: Parse the TCP communication between an IOC and a MCU and format it into a dictionary. "demo.py" shows how this data can be easily visualized for analysis.
### IOC startup script
@@ -33,15 +22,13 @@ turboPmac exports the following IOC shell functions:
The full turboPmacX.cmd file looks like this:
```bash
```
# Define the name of the controller and the corresponding port
epicsEnvSet("DRIVER_PORT","turboPmacX")
epicsEnvSet("IP_PORT","p$(DRIVER_PORT)")
# 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.
# We do not use the standard asyn port driver here, but a PMAC-specific one
# which enables the usage of StreamDevices for
# 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.
# We do not use the standard asyn port driver here, but a PMAC-specific one which enables the usage of StreamDevices for
# communicating with the controller directly.
pmacAsynIPPortConfigure("$(IP_PORT)","172.28.101.24:1025")
@@ -61,8 +48,7 @@ turboPmacAxis("$(DRIVER_PORT)",5);
# Set the number of subsequent timeouts
setMaxSubsequentTimeouts("$(DRIVER_PORT)", 20);
# Configure the timeout frequency watchdog: A maximum of 10 timeouts are allowed
# in 300 seconds before an alarm message is sent.
# Configure the timeout frequency watchdog: A maximum of 10 timeouts are allowed in 300 seconds before an alarm message is sent.
setThresholdComTimeout("$(DRIVER_PORT)", 300, 10);
# Parametrize the EPICS record database with the substitution file named after the MCU.
@@ -75,58 +61,14 @@ dbLoadRecords("$(turboPmac_DB)/asynRecord.db","P=$(INSTR)$(DRIVER_PORT),PORT=$(I
### Additional records
`turboPmac` provides a variety of additional records. See `db/turboPmac.db` for
the complete list and the documentation.
`turboPmac` provides a variety of additional records. See `db/turboPmac.db` for the complete list and the documentation.
## Developer guide
### Versioning
Please see the documentation for the module sinqMotor:
https://gitea.psi.ch/lin-epics-modules/sinqMotor/src/branch/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 sinqMotor, 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://gitea.psi.ch/lin-epics-modules/sinqMotor/src/branch/main/README.md for
more details.
## FAQ
### Axis hardware error at startup
If at IOC startup you are met with the following errors:
```
2026/02/02 16:56:49.470 Controller "turboPmac1", axis 3 => asynStatus turboPmacAxis::handleError(int, char*, int), line 735
Driver hardware error triggered.
2026/02/02 16:56:49.485 Controller "turboPmac1", axis 7 => asynStatus turboPmacAxis::handleError(int, char*, int), line 735
Driver hardware error triggered.
2026/02/02 16:56:49.492 Controller "turboPmac1", axis 8 => asynStatus turboPmacAxis::handleError(int, char*, int), line 735
Driver hardware error triggered.
```
You may need to reset to do a axis reset. This can be done with
`utils/writeRead.py`. The following example does so for axis 3.
Confirm the hardware error:
```
utils/writeRead.py 172.28.87.24:1025 P0301 # 03 specifies axis 3, 01 is the command to read out the error.
# If it returns 13 it's a hardware error.
# For the full list of errors see TurboPMAC_manual.pdf or src/turboPmacAxis.cpp, function "handleError".
```
Reset the axis:
```
utils/writeRead.py 172.28.87.24:1025 P0301=0
```
Check if the error has appeared again:
```
utils/writeRead.py 172.28.87.24:1025 P0301
```
If the console output is not 0, the error has been deleted, but appeared
immediately again. In this case, the error cannot be reset remotely.
If it is an error such as 10 (limit switches hit), the motor needs to be
moved away from the limits, this resets the error automatically.
Other errors like 13 represent an actual issue on the hardware which
needs to be resolved by the electronics motion engineers.
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 sinqMotor, 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.

Binary file not shown.

View File

@@ -230,7 +230,8 @@ static asynStatus sendPmacGetBuffer(pmacPvt *pPmacPvt, asynUser *pasynUser,
// =============================================================================
int pmacAsynIPPortConfigure(const char *portName, const char *hostInfo) {
epicsShareFunc int pmacAsynIPPortConfigure(const char *portName,
const char *hostInfo) {
asynStatus status = asynSuccess;
asynInterface *int32LowerLevelInterface = NULL;
asynInterface *octetLowerLevelInterface = NULL;

View File

@@ -2,7 +2,6 @@
#define asynInterposePmac_H
#include <epicsExport.h>
#include <macros.h>
#include <shareLib.h>
/*
@@ -26,7 +25,8 @@ extern "C" {
* 172.23.243.156:1025)
* @return status
*/
int HIDDEN pmacAsynIPPortConfigure(const char *portName, const char *hostInfo);
epicsShareFunc int pmacAsynIPPortConfigure(const char *portName,
const char *hostInfo);
#ifdef __cplusplus
}

View File

@@ -15,14 +15,6 @@
struct turboPmacAxisImpl {
bool waitForHandshake;
/*
This flag is set to true, if the controller is currently enabling /
disabling the motor and false otherwise. This flag is used in the doPoll
method to check if the enableRBV PV should be set or not in order to prevent
a premature change of state (controller reports that the motor is already
enabled / disabled while it is not ready yet to receive new commands).
*/
bool enableDisable;
time_t timeAtHandshake;
// The axis status is used when enabling / disabling the motor
int axisStatus;
@@ -70,13 +62,11 @@ turboPmacAxis::turboPmacAxis(turboPmacController *pC, int axisNo,
axes.push_back(this);
}
pTurboPmacA_ = std::make_unique<turboPmacAxisImpl>((turboPmacAxisImpl){
.waitForHandshake = false,
.enableDisable = false,
pTurboPmacA_ = std::make_unique<turboPmacAxisImpl>(
(turboPmacAxisImpl){.waitForHandshake = false,
.timeAtHandshake = 0,
.axisStatus = 0,
.needInit = false,
});
.needInit = false});
// Provide initial values for some parameter library entries
status = pC_->setIntegerParam(axisNo_, pC_->rereadEncoderPosition(), 0);
@@ -133,16 +123,9 @@ asynStatus turboPmacAxis::init() {
double motorVelocity = 0.0;
double motorVmax = 0.0;
double motorAccel = 0.0;
// Conversion factor between engineering units (EU) and encoder counts
double counts_to_eu = 0.0;
// Deadband in counts. Can be a fraction of a count, hence double.
double deadband_counts = 0.0;
// Offset time for the movement watchdog caused by the air cushions in
// milliseconds.
int acoDelay = 0.0;
int acoDelay = 0.0; // Offset time for the movement watchdog caused by
// the air cushions in milliseconds.
int axStatus = 0;
// The parameter library takes some time to be initialized. Therefore we
// wait until the status is not asynParamUndefined anymore.
@@ -171,46 +154,27 @@ asynStatus turboPmacAxis::init() {
/*
Read out the axis status, the current position, current and maximum speed,
acceleration, the air cushion delay, the scaling factor between counts and
engineering units ([Qxx00] = engineering units / counts) and the deadband
Ixx65.
acceleration and the air cushion delay.
*/
snprintf(command, sizeof(command),
"Q%2.2d10 Q%2.2d03 Q%2.2d04 Q%2.2d06 P%2.2d22 Q%2.2d00 I%2.2d65",
axisNo_, axisNo_, axisNo_, axisNo_, axisNo_, axisNo_, axisNo_);
status = pC_->writeRead(axisNo_, command, response, 7);
msgPrintControlKey keyDisconnected = msgPrintControlKey(
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, 1);
"P%2.2d00 Q%2.2d10 Q%2.2d03 Q%2.2d04 Q%2.2d06 P%2.2d22", axisNo_,
axisNo_, axisNo_, axisNo_, axisNo_, axisNo_);
status = pC_->writeRead(axisNo_, command, response, 6);
if (status != asynSuccess) {
if (pC_->getMsgPrintControl().shouldBePrinted(keyDisconnected, true,
pC_->pasynUser())) {
asynPrint(
pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nCould not "
"communicate with controller during IOC initialization (error "
"%s). Did you use \"drvAsynIPPortConfigure\" instead of "
"\"pmacAsynIPPortConfigure\" in the .cmd file to create the "
"port driver?\n",
pC_->portName, axisNo(), __PRETTY_FUNCTION__, __LINE__,
pC_->stringifyAsynStatus(status));
}
"Controller \"%s\", axis %d => %s, line %d\nCould not communicate "
"with controller during IOC initialization. Check if you used "
"\"pmacAsynIPPortConfigure\" instead of the standard "
"\"drvAsynIPPortConfigure\" function in the .cmd file in order to "
"create the port driver.\n",
pC_->portName, axisNo(), __PRETTY_FUNCTION__, __LINE__);
pTurboPmacA_->needInit = true;
return status;
}
nvals = sscanf(response, "%d %lf %lf %lf %lf %d", &axStatus, &motorPos,
&motorVmax, &motorVelocity, &motorAccel, &acoDelay);
// No error has been detected -> Reset the error count
if (status == asynSuccess) {
pC_->getMsgPrintControl().resetCount(keyDisconnected, pC_->pasynUser());
}
nvals = sscanf(response, "%lf %lf %lf %lf %d %lf %lf", &motorPos,
&motorVmax, &motorVelocity, &motorAccel, &acoDelay,
&counts_to_eu, &deadband_counts);
// The acoDelay is given in milliseconds -> Convert to seconds, rounded
// up
// The acoDelay is given in milliseconds -> Convert to seconds, rounded up
setOffsetMovTimeout(std::ceil(acoDelay / 1000.0));
// The PMAC electronic specifies the acceleration in m/s^2. Since we
@@ -218,7 +182,7 @@ asynStatus turboPmacAxis::init() {
// here to mm/s^2.
motorAccel = motorAccel * 1000;
if (nvals != 7) {
if (nvals != 6) {
return pC_->couldNotParseResponse(command, response, axisNo_,
__PRETTY_FUNCTION__, __LINE__);
}
@@ -242,35 +206,14 @@ asynStatus turboPmacAxis::init() {
return status;
}
/*
Check if the new target position is within the range current position
+/- deadband. If that is the case, no movement command should be sent.
This functionality is implemented within the motor record itself, we
just need to populate the field SPDP (see record PushSPDB2Field in
db/turboPmac.db)
First, the deadband is read out (Ixx65, see Turbo PMAC User Manual, p.
160). This value then needs to be converted into counts (by dividing it
by 16). After words, the deadband in counts then need to be converted
into engineering units. For this, the scaling factor Qxx10 needs to be
applied (see SINQ_2G_MCU_SW_manual_rev7, p. 17): [Qxx10] = engineering
units (eu) / counts deadband_eu = Qxx10 * Ixx65 / 16
The values of Qxx10 and Ixx65 are read out during initialization and are
assumed to not change during operation.
*/
setAxisParamChecked(this, motorPositionDeadband,
counts_to_eu * deadband_counts / 16.0);
// Update the parameter library immediately
status = callParamCallbacks();
if (status != asynSuccess) {
// If we can't communicate with the parameter library, it doesn't
// make sense to try and upstream this to the user -> Just log the
// error
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line "
"%d\ncallParamCallbacks "
// If we can't communicate with the parameter library, it doesn't make
// sense to try and upstream this to the user -> Just log the error
asynPrint(
pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\ncallParamCallbacks "
"failed with %s.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
pC_->stringifyAsynStatus(status));
@@ -310,6 +253,9 @@ asynStatus turboPmacAxis::doPoll(bool *moving) {
double lowLimit = 0.0;
double limitsOffset = 0.0;
// Was the axis idle during the previous poll?
int previousStatusDone = 1;
// =========================================================================
if (pTurboPmacA_->needInit) {
@@ -358,13 +304,13 @@ asynStatus turboPmacAxis::doPoll(bool *moving) {
}
if (error != 0) {
// If an error occurred while waiting for the handshake, abort
// the waiting procedure and continue with the poll in order to
// handle the error.
// If an error occurred while waiting for the handshake, abort the
// waiting procedure and continue with the poll in order to handle
// the error.
pTurboPmacA_->waitForHandshake = false;
} else if (handshakePerformed == 1) {
// Handshake has been performed successfully -> Continue with
// the poll
// Handshake has been performed successfully -> Continue with the
// poll
pTurboPmacA_->waitForHandshake = false;
} else {
// Still waiting for the handshake - try again in the next busy
@@ -386,6 +332,8 @@ asynStatus turboPmacAxis::doPoll(bool *moving) {
return status;
}
getAxisParamChecked(this, motorStatusDone, &previousStatusDone);
// Query the axis status
snprintf(command, sizeof(command),
"P%2.2d00 Q%2.2d10 P%2.2d01 Q%2.2d13 Q%2.2d14", axisNo_, axisNo_,
@@ -404,16 +352,15 @@ asynStatus turboPmacAxis::doPoll(bool *moving) {
/*
The axis limits are set as: ({[]})
where [] are the positive and negative limits set in EPICS/NICOS, {} are
the software limits set on the MCU and () are the hardware limit
switches. In other words, the EPICS/NICOS limits should be stricter than
the software limits on the MCU which in turn should be stricter than the
hardware limit switches. For example, if the hardware limit switches are
at [-10, 10], the software limits could be at [-9, 9] and the EPICS /
NICOS limits could be at
where [] are the positive and negative limits set in EPICS/NICOS, {} are the
software limits set on the MCU and () are the hardware limit switches. In
other words, the EPICS/NICOS limits should be stricter than the software
limits on the MCU which in turn should be stricter than the hardware limit
switches. For example, if the hardware limit switches are at [-10, 10], the
software limits could be at [-9, 9] and the EPICS / NICOS limits could be at
[-8, 8]. Therefore, we cannot use the software limits read from the MCU
directly, but need to shrink them a bit. In this case, we're shrinking
them by limitsOffset on both sides.
directly, but need to shrink them a bit. In this case, we're shrinking them
by limitsOffset on both sides.
*/
getAxisParamChecked(this, motorLimitsOffset, &limitsOffset);
highLimit = highLimit - limitsOffset;
@@ -422,15 +369,12 @@ asynStatus turboPmacAxis::doPoll(bool *moving) {
// Store the axis status
pTurboPmacA_->axisStatus = axStatus;
// Update the enablement PV, if we are not in the middle of a enabling /
// disabling procedure.
if (!(pTurboPmacA_->enableDisable)) {
// Update the enablement PV
setAxisParamChecked(this, motorEnableRBV,
(axStatus != -3 && axStatus != -5));
}
// Create the unique callsite identifier manually so it can be used
// later in the shouldBePrinted calls.
// Create the unique callsite identifier manually so it can be used later in
// the shouldBePrinted calls.
msgPrintControlKey keyStatus = msgPrintControlKey(
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__);
bool resetCountStatus = true;
@@ -451,9 +395,9 @@ asynStatus turboPmacAxis::doPoll(bool *moving) {
if (pC_->getMsgPrintControl().shouldBePrinted(keyStatus, true,
pC_->pasynUser())) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line "
"%d\nEmergency stop "
asynPrint(
pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nEmergency stop "
"activated.%s\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
pC_->getMsgPrintControl().getSuffix());
@@ -526,8 +470,8 @@ asynStatus turboPmacAxis::doPoll(bool *moving) {
*moving = false;
break;
case 15:
// Move is interrupted, but the motor is not ready to receive
// another move command. Therefore it is treated as still moving.
// Move is interrupted, but the motor is not ready to receive another
// move command. Therefore it is treated as still moving.
*moving = true;
break;
case 16:
@@ -570,6 +514,11 @@ asynStatus turboPmacAxis::doPoll(bool *moving) {
errorStatus = handleError(error, userMessage, sizeof(userMessage));
// Update the parameter library
if (error != 0) {
setAxisParamChecked(this, motorStatusProblem, true);
}
if (*moving == false) {
setAxisParamChecked(this, motorMoveToHome, false);
}
@@ -582,10 +531,8 @@ asynStatus turboPmacAxis::doPoll(bool *moving) {
getAxisParamChecked(this, limFromHardware, &limFromHardware);
if (limFromHardware != 0) {
status = setLimits(highLimit, lowLimit);
if (status != asynSuccess) {
return status;
}
setAxisParamChecked(this, motorHighLimitFromDriver, highLimit);
setAxisParamChecked(this, motorLowLimitFromDriver, lowLimit);
}
status = setMotorPosition(currentPosition);
@@ -599,6 +546,11 @@ asynStatus turboPmacAxis::handleError(int error, char *userMessage,
int sizeUserMessage) {
asynStatus status = asynError;
// Create the unique callsite identifier manually so it can be used later in
// the shouldBePrinted calls.
msgPrintControlKey keyError = msgPrintControlKey(
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__);
switch (error) {
case 0:
status = asynSuccess;
@@ -607,8 +559,7 @@ asynStatus turboPmacAxis::handleError(int error, char *userMessage,
case 1:
// EPICS should already prevent this issue in the first place,
// since it contains the user limits
if (pC_->getMsgPrintControl().shouldBePrinted(
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, true,
if (pC_->getMsgPrintControl().shouldBePrinted(keyError, true,
pC_->pasynUser())) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nTarget "
@@ -621,14 +572,13 @@ asynStatus turboPmacAxis::handleError(int error, char *userMessage,
break;
case 5:
// Command not possible
if (pC_->getMsgPrintControl().shouldBePrinted(
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, true,
if (pC_->getMsgPrintControl().shouldBePrinted(keyError, true,
pC_->pasynUser())) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
asynPrint(
pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nAxis is "
"still moving, but received another move command. EPICS "
"should prevent this, check if *moving is set "
"correctly.%s\n",
"should prevent this, check if *moving is set correctly.%s\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
pC_->getMsgPrintControl().getSuffix());
}
@@ -637,53 +587,46 @@ asynStatus turboPmacAxis::handleError(int error, char *userMessage,
"moving. Please call the support.");
break;
case 8:
if (pC_->getMsgPrintControl().shouldBePrinted(
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, true,
if (pC_->getMsgPrintControl().shouldBePrinted(keyError, true,
pC_->pasynUser())) {
asynPrint(
pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nLost feedback "
"from auxiliary device during movement (P%2.2d01 = "
"%d).%s\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, axisNo_,
error, pC_->getMsgPrintControl().getSuffix());
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nAir cushion "
"feedback stopped during movement (P%2.2d01 = %d).%s\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
axisNo_, error, pC_->getMsgPrintControl().getSuffix());
}
snprintf(userMessage, sizeUserMessage,
"Lost feedback from auxiliary device during movement "
"(P%2.2d01 = "
"Air cushion feedback stopped during movement (P%2.2d01 = "
"%d). Please call the support.",
axisNo_, error);
setAxisParamChecked(this, motorMessageText, userMessage);
break;
case 9:
if (pC_->getMsgPrintControl().shouldBePrinted(
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, true,
if (pC_->getMsgPrintControl().shouldBePrinted(keyError, true,
pC_->pasynUser())) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nNo feedback "
"from "
"auxiliary device before movement (P%2.2d01 = %d).%s\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
axisNo_, error, pC_->getMsgPrintControl().getSuffix());
asynPrint(
pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nNo air cushion "
"feedback before movement start (P%2.2d01 = %d).%s\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, axisNo_,
error, pC_->getMsgPrintControl().getSuffix());
}
snprintf(
userMessage, sizeUserMessage,
"No feedback from auxiliary device before movement (P%2.2d01 = "
snprintf(userMessage, sizeUserMessage,
"No air cushion feedback before movement start (P%2.2d01 = "
"%d). Please call the support.",
axisNo_, error);
setAxisParamChecked(this, motorMessageText, userMessage);
break;
case 10:
/*
Software limits of the controller have been hit. Since the EPICS
limits are derived from the software limits and are a little bit
smaller, this error case can only happen if either the axis has an
incremental encoder which is not properly homed or if a bug occured.
Software limits of the controller have been hit. Since the EPICS limits
are derived from the software limits and are a little bit smaller, this
error case can only happen if either the axis has an incremental encoder
which is not properly homed or if a bug occured.
*/
if (pC_->getMsgPrintControl().shouldBePrinted(
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, true,
if (pC_->getMsgPrintControl().shouldBePrinted(keyError, true,
pC_->pasynUser())) {
asynPrint(
pC_->pasynUser(), ASYN_TRACE_ERROR,
@@ -703,12 +646,11 @@ asynStatus turboPmacAxis::handleError(int error, char *userMessage,
case 11:
// Following error
if (pC_->getMsgPrintControl().shouldBePrinted(
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, true,
if (pC_->getMsgPrintControl().shouldBePrinted(keyError, true,
pC_->pasynUser())) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nMaximum "
"allowed "
asynPrint(
pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nMaximum allowed "
"following error exceeded.%s\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
pC_->getMsgPrintControl().getSuffix());
@@ -722,8 +664,7 @@ asynStatus turboPmacAxis::handleError(int error, char *userMessage,
break;
case 12:
if (pC_->getMsgPrintControl().shouldBePrinted(
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, true,
if (pC_->getMsgPrintControl().shouldBePrinted(keyError, true,
pC_->pasynUser())) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nSecurity "
@@ -742,8 +683,7 @@ asynStatus turboPmacAxis::handleError(int error, char *userMessage,
case 13:
// Driver hardware error triggered
if (pC_->getMsgPrintControl().shouldBePrinted(
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, true,
if (pC_->getMsgPrintControl().shouldBePrinted(keyError, true,
pC_->pasynUser())) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nDriver "
@@ -751,17 +691,16 @@ asynStatus turboPmacAxis::handleError(int error, char *userMessage,
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
pC_->getMsgPrintControl().getSuffix());
}
snprintf(userMessage, sizeUserMessage,
"Driver hardware error (P%2.2d01 = 13). Please call the "
"support.",
snprintf(
userMessage, sizeUserMessage,
"Driver hardware error (P%2.2d01 = 13). Please call the support.",
axisNo_);
setAxisParamChecked(this, motorMessageText, userMessage);
break;
case 14:
// EPICS should already prevent this issue in the first place,
// since it contains the user limits
if (pC_->getMsgPrintControl().shouldBePrinted(
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, true,
if (pC_->getMsgPrintControl().shouldBePrinted(keyError, true,
pC_->pasynUser())) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nMove "
@@ -777,8 +716,7 @@ asynStatus turboPmacAxis::handleError(int error, char *userMessage,
break;
default:
if (pC_->getMsgPrintControl().shouldBePrinted(
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, true,
if (pC_->getMsgPrintControl().shouldBePrinted(keyError, true,
pC_->pasynUser())) {
asynPrint(
pC_->pasynUser(), ASYN_TRACE_ERROR,
@@ -790,6 +728,10 @@ asynStatus turboPmacAxis::handleError(int error, char *userMessage,
setAxisParamChecked(this, motorMessageText, userMessage);
break;
}
if (status == asynSuccess) {
pC_->getMsgPrintControl().resetCount(keyError, pC_->pasynUser());
}
return status;
}
@@ -802,7 +744,7 @@ asynStatus turboPmacAxis::doMove(double position, int relative,
char command[pC_->MAXBUF_] = {0};
char response[pC_->MAXBUF_] = {0};
double motorTargetPosition = 0.0;
double motorCoordinatesPosition = 0.0;
double motorRecResolution = 0.0;
double motorVelocity = 0.0;
int enabled = 0;
@@ -811,37 +753,32 @@ asynStatus turboPmacAxis::doMove(double position, int relative,
// =========================================================================
// Suppress unused variables warning
(void)minVelocity;
(void)acceleration;
getAxisParamChecked(this, motorEnableRBV, &enabled);
getAxisParamChecked(this, motorRecResolution, &motorRecResolution);
if (enabled == 0) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nAxis is "
"disabled.\n",
asynPrint(
pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nAxis is disabled.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__);
return asynSuccess;
}
// Convert from EPICS to user / motor units
motorTargetPosition = position * motorRecResolution;
motorCoordinatesPosition = position * motorRecResolution;
motorVelocity = maxVelocity * motorRecResolution;
asynPrint(pC_->pasynUser(), ASYN_TRACE_FLOW,
"Controller \"%s\", axis %d => %s, line %d\nStart of axis to "
"position %lf.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
motorTargetPosition);
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, position);
// Check if the speed is allowed to be changed
getAxisParamChecked(this, motorCanSetSpeed, &motorCanSetSpeed);
// Prepend the new motor speed, if the user is allowed to set the speed.
// Mind the " " (space) before the closing "", as the command created
// here is prepended to the one down below.
// Mind the " " (space) before the closing "", as the command created here
// is prepended to the one down below.
if (motorCanSetSpeed != 0) {
snprintf(command, sizeof(command), "Q%2.2d04=%lf ", axisNo_,
motorVelocity);
@@ -854,34 +791,34 @@ asynStatus turboPmacAxis::doMove(double position, int relative,
motorVelocity);
}
// Perform handshake, Set target position (and speed, if allowed) and
// start the move command
// Perform handshake, Set target position (and speed, if allowed) and start
// the move command
if (relative) {
snprintf(&command[writeOffset], sizeof(command) - writeOffset,
"P%2.2d23=0 Q%2.2d02=%lf M%2.2d=2", axisNo_, axisNo_,
motorTargetPosition, axisNo_);
motorCoordinatesPosition, axisNo_);
} else {
snprintf(&command[writeOffset], sizeof(command) - writeOffset,
"P%2.2d23=0 Q%2.2d01=%lf M%2.2d=1", axisNo_, axisNo_,
motorTargetPosition, axisNo_);
motorCoordinatesPosition, axisNo_);
}
// We don't expect an answer
status = pC_->writeRead(axisNo_, command, response, 0);
if (status != asynSuccess) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nStarting "
"movement to "
asynPrint(
pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nStarting movement to "
"target position %lf failed.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
motorTargetPosition);
motorCoordinatesPosition);
setAxisParamChecked(this, motorStatusProblem, true);
return status;
}
// In the next poll, we will check if the handshake has been performed
// in a reasonable time
// In the next poll, we will check if the handshake has been performed in a
// reasonable time
pTurboPmacA_->waitForHandshake = true;
pTurboPmacA_->timeAtHandshake = time(NULL);
@@ -896,6 +833,7 @@ asynStatus turboPmacAxis::doMove(double position, int relative,
asynStatus turboPmacAxis::stop(double acceleration) {
// Status of read-write-operations of ASCII commands to the controller
asynStatus status = asynSuccess;
char command[pC_->MAXBUF_] = {0};
@@ -903,37 +841,22 @@ asynStatus turboPmacAxis::stop(double acceleration) {
// =========================================================================
// Suppress unused variables warning
(void)acceleration;
snprintf(command, sizeof(command), "M%2.2d=8", axisNo_);
status = pC_->writeRead(axisNo_, command, response, 0);
if (status != asynSuccess) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nStopping "
"the movement "
asynPrint(
pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nStopping the movement "
"failed.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__);
setAxisParamChecked(this, motorStatusProblem, true);
}
// Reset the driver to idle state and move out of the handshake wait
// loop, if we're currently inside it.
// Reset the driver to idle state and move out of the handshake wait loop,
// if we're currently inside it.
pTurboPmacA_->waitForHandshake = false;
/*
Stopping the motor results in a movement and further move commands have
to wait until the stopping movement is done. Therefore, we need to wait
until the poller "sees" the changed state (otherwise, we risk issuing
move commands while the motor is stopping). To ensure that at least one
poll is done, this thread (which also runs move commands) is paused for
twice the idle poll period.
*/
unsigned int idlePollMicros =
(unsigned int)ceil(pC_->idlePollPeriod() * 1e6);
usleep(2 * idlePollMicros);
return status;
}
@@ -959,18 +882,17 @@ asynStatus turboPmacAxis::doReset() {
setAxisParamChecked(this, motorStatusProblem, true);
}
// Reset the driver to idle state and move out of the handshake wait
// loop, if we're currently inside it.
// Reset the driver to idle state and move out of the handshake wait loop,
// if we're currently inside it.
pTurboPmacA_->waitForHandshake = false;
// Disable the axis
return enable(false);
return status;
}
/*
Home the axis. On absolute encoder systems, this is a no-op
*/
asynStatus turboPmacAxis::doHome(double minVelocity, double maxVelocity,
asynStatus turboPmacAxis::doHome(double min_velocity, double max_velocity,
double acceleration, int forwards) {
// Status of read-write-operations of ASCII commands to the controller
@@ -981,12 +903,6 @@ asynStatus turboPmacAxis::doHome(double minVelocity, double maxVelocity,
// =========================================================================
// Suppress unused variables warning
(void)minVelocity;
(void)maxVelocity;
(void)acceleration;
(void)forwards;
getAxisParamChecked(this, encoderType, &response);
// Only send the home command if the axis has an incremental encoder
@@ -1058,14 +974,13 @@ asynStatus turboPmacAxis::readEncoderType() {
}
/*
This is not a command that can always be run when enabling a motor as it
also causes relative encoders to reread a position necessitating
recalibration. We only want it to run on absolute encoders. We also want it
to be clear to instrument scientists, that power has to be cut to the motor,
in order to reread the encoder as not all motors have breaks and they may
start to move when disabled. For that reason, we don't automatically disable
the motors to run the command and instead require that the scientists first
disable the motor.
This is not a command that can always be run when enabling a motor as it also
causes relative encoders to reread a position necessitating recalibration. We
only want it to run on absolute encoders. We also want it to be clear to
instrument scientists, that power has to be cut to the motor, in order to reread
the encoder as not all motors have breaks and they may start to move when
disabled. For that reason, we don't automatically disable the motors to run the
command and instead require that the scientists first disable the motor.
*/
asynStatus turboPmacAxis::rereadEncoder() {
char command[pC_->MAXBUF_] = {0};
@@ -1109,12 +1024,11 @@ asynStatus turboPmacAxis::rereadEncoder() {
return asynError;
} else {
snprintf(command, sizeof(command), "M%2.2d=15", axisNo_);
asynPrint(pC_->pasynUser(), ASYN_TRACE_FLOW,
"Controller \"%s\", axis %d => %s, line %d\nRereading "
"absolute "
asynPrint(
pC_->pasynUser(), ASYN_TRACE_FLOW,
"Controller \"%s\", axis %d => %s, line %d\nRereading absolute "
"encoder via command %s.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
command);
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, command);
pC_->writeRead(axisNo_, command, response, 0);
}
@@ -1123,15 +1037,15 @@ asynStatus turboPmacAxis::rereadEncoder() {
// it is actually finished, so we instead wait for 0.5 seconds.
usleep(500000);
// Turn off parameter as finished rereading, this will only be
// immediately noticed in the read back variable though
// Turn off parameter as finished rereading, this will only be immediately
// noticed in the read back variable though
setAxisParamChecked(this, rereadEncoderPosition, false);
return asynSuccess;
}
asynStatus turboPmacAxis::enable(bool on) {
int timeout_enable_disable = 5;
int timeout_enable_disable = 2;
char command[pC_->MAXBUF_] = {0};
char response[pC_->MAXBUF_] = {0};
int nvals = 0;
@@ -1144,41 +1058,37 @@ asynStatus turboPmacAxis::enable(bool on) {
/*
Continue regardless of the status returned by the poll; we just want to
find out whether the motor is currently moving or not. If the poll
function fails before it can determine that, it is assumed that the
motor is not moving.
function fails before it can determine that, it is assumed that the motor
is not moving.
*/
bool moving = false;
doPoll(&moving);
// If the axis is currently moving, it cannot be disabled. Ignore the
// command and inform the user. We check the last known status of the
// axis instead of "moving", since status -6 is also moving, but the
// motor can actually be disabled in this state!
// command and inform the user. We check the last known status of the axis
// instead of "moving", since status -6 is also moving, but the motor can
// actually be disabled in this state!
int axStatus = pTurboPmacA_->axisStatus;
if (axStatus == 1 || axStatus == 2 || axStatus == 3 || axStatus == 4 ||
axStatus == 5 || axStatus == 6 || axStatus == 7 || axStatus == 8 ||
axStatus == 9 || axStatus == 10 || axStatus == 11 || axStatus == 12 ||
axStatus == 13 || axStatus == 15 || axStatus == 16) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nAxis is not "
"idle (status %d) and can therefore not be enabled / "
"disabled.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
axStatus);
"idle and can therefore not be enabled / disabled.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__);
setAxisParamChecked(this, motorMessageText,
"Axis cannot be disabled while it is moving.");
pTurboPmacA_->enableDisable = false;
return asynError;
}
// Axis is already enabled / disabled and a new enable / disable command
// was sent => Do nothing
if ((axStatus != -3) == on) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_WARNING,
"Controller \"%s\", axis %d => %s, line %d\nAxis is "
"already %s.\n",
asynPrint(
pC_->pasynUser(), ASYN_TRACE_WARNING,
"Controller \"%s\", axis %d => %s, line %d\nAxis is already %s.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
on ? "enabled" : "disabled");
return asynSuccess;
@@ -1192,10 +1102,7 @@ asynStatus turboPmacAxis::enable(bool on) {
}
}
// Now the actual enabling / disabling starts
pTurboPmacA_->enableDisable = true;
// Enable / disable the axis
// Enable / disable the axis if it is not moving
snprintf(command, sizeof(command), "M%2.2d14=%d", axisNo_, on);
asynPrint(pC_->pasynUser(), ASYN_TRACE_FLOW,
"Controller \"%s\", axis %d => %s, line %d\n%s axis\n",
@@ -1213,14 +1120,10 @@ asynStatus turboPmacAxis::enable(bool on) {
int startTime = time(NULL);
while (time(NULL) < startTime + timeout_enable_disable) {
// Wait a bit between status updates from the controller.
usleep(10000);
// Read the axis status
usleep(100000);
status = pC_->writeRead(axisNo_, command, response, 1);
if (status != asynSuccess) {
// Enabling / disabling procedure failed
pTurboPmacA_->enableDisable = false;
return status;
}
nvals = sscanf(response, "%d", &pTurboPmacA_->axisStatus);
@@ -1230,13 +1133,9 @@ asynStatus turboPmacAxis::enable(bool on) {
}
if ((pTurboPmacA_->axisStatus != -3) == on) {
usleep(500000);
bool moving = false;
forcedPoll(&moving);
// Enabling / disabling procedure is completed (successfully)
pTurboPmacA_->enableDisable = false;
// Perform a poll to update the parameter library
poll(&moving);
return asynSuccess;
}
}
@@ -1253,9 +1152,6 @@ asynStatus turboPmacAxis::enable(bool on) {
snprintf(command, sizeof(command), "Failed to %s within %d seconds",
on ? "enable" : "disable", timeout_enable_disable);
setAxisParamChecked(this, motorMessageText, command);
// Enabling / disabling procedure failed
pTurboPmacA_->enableDisable = false;
return asynError;
}
@@ -1329,8 +1225,8 @@ asynStatus turboPmacCreateAxis(const char *portName, int axis) {
https://github.com/epics-modules/motor/blob/master/motorApp/MotorSrc/asynMotorController.cpp
https://github.com/epics-modules/asyn/blob/master/asyn/asynPortDriver/asynPortDriver.cpp
The created object is registered in EPICS in its constructor and can
safely be "leaked" here.
The created object is registered in EPICS in its constructor and can safely
be "leaked" here.
*/
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-variable"
@@ -1353,15 +1249,14 @@ static const iocshArg *const CreateAxisArgs[] = {&CreateAxisArg0,
static const iocshFuncDef configTurboPmacCreateAxis = {
"turboPmacAxis", 2, CreateAxisArgs,
"Create an instance of a turboPmac axis. The first argument is the "
"controller this axis should be attached to, the second argument is "
"the "
"controller this axis should be attached to, the second argument is the "
"axis number."};
static void configTurboPmacCreateAxisCallFunc(const iocshArgBuf *args) {
turboPmacCreateAxis(args[0].sval, args[1].ival);
}
// This function is made known to EPICS in turboPmac.dbd and is called by
// EPICS in order to register both functions in the IOC shell
// This function is made known to EPICS in turboPmac.dbd and is called by EPICS
// in order to register both functions in the IOC shell
static void turboPmacAxisRegister(void) {
iocshRegister(&configTurboPmacCreateAxis,
configTurboPmacCreateAxisCallFunc);

View File

@@ -4,19 +4,15 @@
#include "turboPmacController.h"
#include <memory>
struct HIDDEN turboPmacAxisImpl;
struct turboPmacAxisImpl;
class HIDDEN turboPmacAxis : public sinqAxis {
class turboPmacAxis : public sinqAxis {
public:
/**
* @brief Construct a new turboPmacAxis
*
* @param pController Pointer to the associated controller
* @param axisNo Index of the axis
* @param initialize By setting this parameter to false, the
* initialization functions of the axes are not executed. This is e.g.
* necessary when this constructor is called from a children class
* constructor which performs its own initialization.
*/
turboPmacAxis(turboPmacController *pController, int axisNo,
bool initialize = true);
@@ -28,18 +24,6 @@ class HIDDEN turboPmacAxis : public sinqAxis {
*/
virtual ~turboPmacAxis();
/**
* @brief Readout of some values from the controller at IOC startup
*
* The following steps are performed:
* - Read out the motor status, motor position, velocity and acceleration
* from the MCU and store this information in the parameter library.
* - Set the enable PV according to the initial status of the axis.
*
* @return asynStatus
*/
virtual asynStatus init();
/**
* @brief Implementation of the `stop` function from asynMotorAxis
*
@@ -86,6 +70,18 @@ class HIDDEN turboPmacAxis : public sinqAxis {
double min_velocity, double max_velocity,
double acceleration);
/**
* @brief Readout of some values from the controller at IOC startup
*
* The following steps are performed:
* - Read out the motor status, motor position, velocity and acceleration
* from the MCU and store this information in the parameter library.
* - Set the enable PV according to the initial status of the axis.
*
* @return asynStatus
*/
virtual asynStatus init();
/**
* @brief Implementation of the `doReset` function from sinqAxis.
*

View File

@@ -41,13 +41,13 @@ struct turboPmacControllerImpl {
// User for writing int32 values to the port driver.
asynUser *pasynInt32SyncIOipPort;
// Indices of additional ParamLib entries
// Indices of additional PVs
int rereadEncoderPosition;
int readConfig;
int flushHardware;
int limFromHardware;
};
#define NUM_turboPmac_DRIVER_PARAMS 5
#define NUM_turboPmac_DRIVER_PARAMS 3
turboPmacController::turboPmacController(const char *portName,
const char *ipPortConfigName,
@@ -61,17 +61,17 @@ turboPmacController::turboPmacController(const char *portName,
- REREAD_ENCODER_POSITION
- READ_CONFIG
*/
numExtraParams + NUM_turboPmac_DRIVER_PARAMS),
pTurboPmacC_(
numExtraParams + NUM_turboPmac_DRIVER_PARAMS)
{
// The paramLib indices are populated with the calls to createParam
pTurboPmacC_ =
std::make_unique<turboPmacControllerImpl>((turboPmacControllerImpl){
.comTimeout = comTimeout,
.lastResponse = {0},
.pasynInt32SyncIOipPort = nullptr, // Populated in constructor
.rereadEncoderPosition = 0, // Populated in constructor
.readConfig = 0, // Populated in constructor
.flushHardware = 0, // Populated in constructor
.limFromHardware = 0, // Populated in constructor
})) {
});
// Initialization of local variables
asynStatus status = asynSuccess;
@@ -206,6 +206,8 @@ asynStatus turboPmacController::writeRead(int axisNo, const char *command,
// Definition of local variables.
asynStatus status = asynSuccess;
asynStatus timeoutStatus = asynSuccess;
// char fullCommand[MAXBUF_] = {0};
char drvMessageText[MAXBUF_] = {0};
char modResponse[MAXBUF_] = {0};
int motorStatusProblem = 0;
@@ -253,22 +255,6 @@ asynStatus turboPmacController::writeRead(int axisNo, const char *command,
pasynOctetSyncIOipPort(), command, commandLength, response, MAXBUF_,
pTurboPmacC_->comTimeout, &nbytesOut, &nbytesIn, &eomReason);
/*
If sth. is written to the controller, it needs some time to process the
command. However, the controller returns the acknowledgment via
pasynOctetSyncIO->writeRead immediately, signalling to the driver that it is
ready to receive the next command. In practice, this can result in commands
getting discarded on the driver side or in bringing the driver in undefined
states (e.g. stuck in status 1).
To prevent this, we wait for 20 ms after a write command to give the
controller enough time to process everything. A write command can be
identified by looking for the equal sign.
*/
if (strchr(command, '=')) {
usleep(20000);
}
msgPrintControlKey comKey =
msgPrintControlKey(portName, axisNo, __PRETTY_FUNCTION__, __LINE__);
@@ -283,7 +269,8 @@ asynStatus turboPmacController::writeRead(int axisNo, const char *command,
getMsgPrintControl().getSuffix());
}
checkComTimeoutWatchdog(axisNo, drvMessageText, sizeof(drvMessageText));
timeoutStatus = checkComTimeoutWatchdog(axisNo, drvMessageText,
sizeof(drvMessageText));
int timeoutCounter = 0;
while (1) {
@@ -331,10 +318,14 @@ asynStatus turboPmacController::writeRead(int axisNo, const char *command,
axis->setNeedInit(true);
}
if (timeoutStatus == asynError) {
status = asynError;
}
// The message should only ever terminate due to reason 2
msgPrintControlKey terminateKey =
msgPrintControlKey(portName, axisNo, __PRETTY_FUNCTION__, __LINE__);
if (status == asynSuccess && eomReason != 2) {
if (eomReason != 2) {
status = asynError;
char reasonStringified[30] = {0};
@@ -385,7 +376,7 @@ asynStatus turboPmacController::writeRead(int axisNo, const char *command,
}
msgPrintControlKey numResponsesKey =
msgPrintControlKey(portName, axisNo, __PRETTY_FUNCTION__, __LINE__);
if (status == asynSuccess && numExpectedResponses != numReceivedResponses) {
if (numExpectedResponses != numReceivedResponses) {
adjustResponseForPrint(modResponse, response, MAXBUF_);
if (getMsgPrintControl().shouldBePrinted(numResponsesKey, true,
@@ -567,8 +558,7 @@ static const iocshArg *const CreateControllerArgs[] = {
&CreateControllerArg0, &CreateControllerArg1, &CreateControllerArg2,
&CreateControllerArg3, &CreateControllerArg4, &CreateControllerArg5};
static const iocshFuncDef configTurboPmacCreateController = {
"turboPmacController", 6, CreateControllerArgs,
"Create a new instance of a TurboPMAC controller."};
"turboPmacController", 6, CreateControllerArgs};
static void configTurboPmacCreateControllerCallFunc(const iocshArgBuf *args) {
turboPmacCreateController(args[0].sval, args[1].sval, args[2].ival,
args[3].dval, args[4].dval, args[5].dval);

View File

@@ -15,11 +15,11 @@
// Forward declaration of the controller class to resolve the cyclic dependency
// between the controller and the axis .h-file. See
// https://en.cppreference.com/w/cpp/language/class.
class HIDDEN turboPmacAxis;
class turboPmacAxis;
struct HIDDEN turboPmacControllerImpl;
struct turboPmacControllerImpl;
class HIDDEN turboPmacController : public sinqController {
class turboPmacController : public sinqController {
public:
/**
* @brief Construct a new turboPmacController object. This function is meant
@@ -93,8 +93,7 @@ class HIDDEN turboPmacController : public sinqController {
* @return asynStatus
*/
asynStatus writeRead(int axisNo, const char *command, char *response,
int numExpectedResponses)
__attribute__((visibility("hidden")));
int numExpectedResponses);
/**
* @brief Specialized version of sinqController::couldNotParseResponse