Compare commits

..

72 Commits
1.6.3 ... 1.5.2

Author SHA1 Message Date
62ccf046fd Added compiler flags for the C++ code and formatted README
Some checks failed
Test And Build / Lint (push) Failing after 4s
Test And Build / Build (push) Failing after 6s
2025-12-23 13:27:14 +01:00
9d61852713 Fixed number of commands bug in turboPmacAxis::init
Some checks failed
Test And Build / Lint (push) Failing after 4s
Test And Build / Build (push) Successful in 7s
2025-12-23 11:59:09 +01:00
e64cedb243 Updated sinqMotor to fix segfault
Some checks failed
Test And Build / Lint (push) Failing after 4s
Test And Build / Build (push) Successful in 7s
2025-12-23 11:56:42 +01:00
0ff5632112 Added user manual and .gitignore to repo
Some checks failed
Test And Build / Lint (push) Failing after 4s
Test And Build / Build (push) Successful in 7s
2025-11-03 08:37:49 +01:00
f67941d67b Fixed some small bugs
Some checks failed
Test And Build / Lint (push) Failing after 9s
Test And Build / Build (push) Successful in 13s
2025-10-13 16:57:30 +02:00
7a96ed2b71 Fixed bug: Wrong scale used before
Some checks failed
Test And Build / Lint (push) Failing after 3s
Test And Build / Build (push) Successful in 8s
The correct scale from counts to engeering units is Qxx00.
2025-09-10 11:52:51 +02:00
cf43a1c57a Updated to sinqMotor 1.5 to get deadband feature
Some checks failed
Test And Build / Lint (push) Failing after 3s
Test And Build / Build (push) Successful in 7s
2025-09-09 16:52:19 +02:00
85317e24cd Deadband check in driver code 2025-09-09 13:43:11 +02:00
1ee483f8e9 Removed C++ only flag from USR_CFLAGS
Some checks failed
Test And Build / Lint (push) Failing after 3s
Test And Build / Build (push) Successful in 7s
USR_CFLAGS only applies to .c files, which are compiled with a C
compiler, therefore the C++ flag is not applied, resulting in a warning
during compilation.
2025-08-22 15:27:28 +02:00
002b5d2616 Fixed bug with errorprint to ioc console
The previous setup created one print key for all error types. The
problem with that is that if an error directly followed another error
(without an error = 0 in between), the MsgPrintControl prevented
printing of the new error. Now every error has its own key.
2025-08-22 15:25:31 +02:00
ce80426790 Updated sinqMotor to fix bug in forcedPoll
Some checks failed
Test And Build / Lint (push) Failing after 4s
Test And Build / Build (push) Successful in 7s
2025-08-14 17:21:58 +02:00
72f3965881 Fixed imprecise comment
Some checks failed
Test And Build / Lint (push) Failing after 3s
Test And Build / Build (push) Successful in 8s
2025-08-12 15:42:43 +02:00
d41e7bf054 Updated to sinqMotor 1.4.0
Some checks failed
Test And Build / Lint (push) Failing after 3s
Test And Build / Build (push) Successful in 8s
2025-08-12 09:14:38 +02:00
30bfa1cac5 Hide visibility of classes to avoid symbol clashes 2025-08-12 09:00:42 +02:00
235816fd20 Hide symbols via compilerflag 2025-08-11 16:46:22 +02:00
bd0966d2c9 Added flag to see whether we are in the middle of enabling / disabling
Some checks failed
Test And Build / Lint (push) Failing after 9s
Test And Build / Build (push) Successful in 13s
2025-08-11 15:47:27 +02:00
9d5d90574a Added sleep after write commands
See comment in writeRead for explanation.
2025-08-11 15:47:27 +02:00
6effc5e906 Updated sinqMotor to latest version 2025-08-11 15:47:27 +02:00
bc5de11b16 remove unneeded line
Some checks failed
Test And Build / Lint (push) Failing after 3s
Test And Build / Build (push) Successful in 8s
2025-07-04 14:16:48 +02:00
aa488627f9 remove old pipeline file
Some checks failed
Test And Build / Lint (push) Failing after 3s
Test And Build / Build (push) Successful in 9s
2025-07-04 14:12:54 +02:00
ccaee6c5d1 no newline
Some checks failed
Test And Build / Lint (push) Failing after 4s
Test And Build / Build (push) Successful in 8s
2025-07-04 14:07:20 +02:00
323b030581 adds IGNORE_SUBMODULES as other makefile tries to pull everything anew
Some checks failed
Test And Build / Lint (push) Failing after 12s
Test And Build / Build (push) Failing after 3s
2025-07-04 13:54:24 +02:00
b206012df2 time to build
Some checks failed
Test And Build / Build (push) Failing after 5s
2025-07-04 12:57:23 +02:00
21ec7e8467 time to build
Some checks failed
Test And Build / Build (push) Failing after 5s
2025-07-04 12:55:24 +02:00
398bc8241a time to build
Some checks failed
Test And Build / Build (push) Failing after 4s
2025-07-04 12:53:13 +02:00
8ca684604d typo
All checks were successful
Test And Build / Build (push) Successful in 3s
2025-07-04 12:51:04 +02:00
72fe2b3681 it should already be there it is now a submodule...
Some checks failed
Test And Build / Build (push) Failing after 3s
2025-07-04 12:50:28 +02:00
5911e62029 :)
Some checks failed
Test And Build / Build (push) Failing after 40s
2025-07-04 12:45:16 +02:00
682024091f is the url wrong ?
Some checks failed
Test And Build / Build (push) Failing after 31s
2025-07-04 12:43:33 +02:00
335de72bc5 maybe a key is necessary
Some checks failed
Test And Build / Build (push) Failing after 39s
2025-07-04 12:38:57 +02:00
8e5055d6b8 Can I check out another repository?
Some checks failed
Test And Build / Build (push) Failing after 31s
2025-07-04 12:31:40 +02:00
1cab6e14ff Can I check out another repository?
Some checks failed
Test And Build / Lint (push) Failing after 3s
Test And Build / Build (push) Failing after 40s
2025-07-04 12:30:53 +02:00
1e8a6495b8 Disable the axis on reset, if it is not moving
Resetting the axis now also disables it, if it is not moving.
2025-07-01 13:11:06 +02:00
6b91ab6d51 Links in README.md updated to point to Gitea now. 2025-06-26 13:56:57 +02:00
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
5 changed files with 169 additions and 224 deletions

3
.gitmodules vendored
View File

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

View File

@@ -33,7 +33,7 @@ turboPmac exports the following IOC shell functions:
The full turboPmacX.cmd file looks like this: The full turboPmacX.cmd file looks like this:
```bash ```
# Define the name of the controller and the corresponding port # Define the name of the controller and the corresponding port
epicsEnvSet("DRIVER_PORT","turboPmacX") epicsEnvSet("DRIVER_PORT","turboPmacX")
epicsEnvSet("IP_PORT","p$(DRIVER_PORT)") epicsEnvSet("IP_PORT","p$(DRIVER_PORT)")
@@ -94,39 +94,3 @@ 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 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 https://gitea.psi.ch/lin-epics-modules/sinqMotor/src/branch/main/README.md for
more details. 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.

View File

@@ -179,38 +179,22 @@ asynStatus turboPmacAxis::init() {
"Q%2.2d10 Q%2.2d03 Q%2.2d04 Q%2.2d06 P%2.2d22 Q%2.2d00 I%2.2d65", "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_); axisNo_, axisNo_, axisNo_, axisNo_, axisNo_, axisNo_, axisNo_);
status = pC_->writeRead(axisNo_, command, response, 7); status = pC_->writeRead(axisNo_, command, response, 7);
msgPrintControlKey keyDisconnected = msgPrintControlKey(
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, 1);
if (status != asynSuccess) { if (status != asynSuccess) {
if (pC_->getMsgPrintControl().shouldBePrinted(keyDisconnected, true,
pC_->pasynUser())) {
asynPrint( asynPrint(
pC_->pasynUser(), ASYN_TRACE_ERROR, pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nCould not " "Controller \"%s\", axis %d => %s, line %d\nCould not communicate "
"communicate with controller during IOC initialization (error " "with controller during IOC initialization. Check if you used "
"%s). Did you use \"drvAsynIPPortConfigure\" instead of " "\"pmacAsynIPPortConfigure\" instead of the standard "
"\"pmacAsynIPPortConfigure\" in the .cmd file to create the " "\"drvAsynIPPortConfigure\" function in the .cmd file in order to "
"port driver?\n", "create the port driver.\n",
pC_->portName, axisNo(), __PRETTY_FUNCTION__, __LINE__, pC_->portName, axisNo(), __PRETTY_FUNCTION__, __LINE__);
pC_->stringifyAsynStatus(status));
}
pTurboPmacA_->needInit = true; pTurboPmacA_->needInit = true;
return status;
} }
// 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, nvals = sscanf(response, "%lf %lf %lf %lf %d %lf %lf", &motorPos,
&motorVmax, &motorVelocity, &motorAccel, &acoDelay, &motorVmax, &motorVelocity, &motorAccel, &acoDelay,
&counts_to_eu, &deadband_counts); &counts_to_eu, &deadband_counts);
// The acoDelay is given in milliseconds -> Convert to seconds, rounded // The acoDelay is given in milliseconds -> Convert to seconds, rounded up
// up
setOffsetMovTimeout(std::ceil(acoDelay / 1000.0)); setOffsetMovTimeout(std::ceil(acoDelay / 1000.0));
// The PMAC electronic specifies the acceleration in m/s^2. Since we // The PMAC electronic specifies the acceleration in m/s^2. Since we
@@ -218,7 +202,7 @@ asynStatus turboPmacAxis::init() {
// here to mm/s^2. // here to mm/s^2.
motorAccel = motorAccel * 1000; motorAccel = motorAccel * 1000;
if (nvals != 7) { if (nvals != 8) {
return pC_->couldNotParseResponse(command, response, axisNo_, return pC_->couldNotParseResponse(command, response, axisNo_,
__PRETTY_FUNCTION__, __LINE__); __PRETTY_FUNCTION__, __LINE__);
} }
@@ -243,18 +227,18 @@ asynStatus turboPmacAxis::init() {
} }
/* /*
Check if the new target position is within the range current position Check if the new target position is within the range current position +/-
+/- deadband. If that is the case, no movement command should be sent. deadband. If that is the case, no movement command should be sent. This
This functionality is implemented within the motor record itself, we functionality is implemented within the motor record itself, we just need to
just need to populate the field SPDP (see record PushSPDB2Field in populate the field SPDP (see record PushSPDB2Field in db/turboPmac.db)
db/turboPmac.db)
First, the deadband is read out (Ixx65, see Turbo PMAC User Manual, p. 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 160). This value then needs to be converted into counts (by dividing it by
by 16). After words, the deadband in counts then need to be converted 16). After words, the deadband in counts then need to be converted into
into engineering units. For this, the scaling factor Qxx10 needs to be engineering units. For this, the scaling factor Qxx10 needs to be applied
applied (see SINQ_2G_MCU_SW_manual_rev7, p. 17): [Qxx10] = engineering (see SINQ_2G_MCU_SW_manual_rev7, p. 17):
units (eu) / counts deadband_eu = Qxx10 * Ixx65 / 16 [Qxx10] = engineering units (eu) / counts
deadband_eu = Qxx10 * Ixx65 / 16
The values of Qxx10 and Ixx65 are read out during initialization and are The values of Qxx10 and Ixx65 are read out during initialization and are
assumed to not change during operation. assumed to not change during operation.
@@ -265,12 +249,11 @@ asynStatus turboPmacAxis::init() {
// Update the parameter library immediately // Update the parameter library immediately
status = callParamCallbacks(); status = callParamCallbacks();
if (status != asynSuccess) { if (status != asynSuccess) {
// If we can't communicate with the parameter library, it doesn't // If we can't communicate with the parameter library, it doesn't make
// make sense to try and upstream this to the user -> Just log the // sense to try and upstream this to the user -> Just log the error
// error asynPrint(
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR, pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line " "Controller \"%s\", axis %d => %s, line %d\ncallParamCallbacks "
"%d\ncallParamCallbacks "
"failed with %s.\n", "failed with %s.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
pC_->stringifyAsynStatus(status)); pC_->stringifyAsynStatus(status));
@@ -358,13 +341,13 @@ asynStatus turboPmacAxis::doPoll(bool *moving) {
} }
if (error != 0) { if (error != 0) {
// If an error occurred while waiting for the handshake, abort // If an error occurred while waiting for the handshake, abort the
// the waiting procedure and continue with the poll in order to // waiting procedure and continue with the poll in order to handle
// handle the error. // the error.
pTurboPmacA_->waitForHandshake = false; pTurboPmacA_->waitForHandshake = false;
} else if (handshakePerformed == 1) { } else if (handshakePerformed == 1) {
// Handshake has been performed successfully -> Continue with // Handshake has been performed successfully -> Continue with the
// the poll // poll
pTurboPmacA_->waitForHandshake = false; pTurboPmacA_->waitForHandshake = false;
} else { } else {
// Still waiting for the handshake - try again in the next busy // Still waiting for the handshake - try again in the next busy
@@ -404,16 +387,15 @@ asynStatus turboPmacAxis::doPoll(bool *moving) {
/* /*
The axis limits are set as: ({[]}) The axis limits are set as: ({[]})
where [] are the positive and negative limits set in EPICS/NICOS, {} are where [] are the positive and negative limits set in EPICS/NICOS, {} are the
the software limits set on the MCU and () are the hardware limit software limits set on the MCU and () are the hardware limit switches. In
switches. In other words, the EPICS/NICOS limits should be stricter than other words, the EPICS/NICOS limits should be stricter than the software
the software limits on the MCU which in turn should be stricter than the limits on the MCU which in turn should be stricter than the hardware limit
hardware limit switches. For example, if the hardware limit switches are switches. For example, if the hardware limit switches are at [-10, 10], the
at [-10, 10], the software limits could be at [-9, 9] and the EPICS / software limits could be at [-9, 9] and the EPICS / NICOS limits could be at
NICOS limits could be at
[-8, 8]. Therefore, we cannot use the software limits read from the MCU [-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 directly, but need to shrink them a bit. In this case, we're shrinking them
them by limitsOffset on both sides. by limitsOffset on both sides.
*/ */
getAxisParamChecked(this, motorLimitsOffset, &limitsOffset); getAxisParamChecked(this, motorLimitsOffset, &limitsOffset);
highLimit = highLimit - limitsOffset; highLimit = highLimit - limitsOffset;
@@ -429,8 +411,8 @@ asynStatus turboPmacAxis::doPoll(bool *moving) {
(axStatus != -3 && axStatus != -5)); (axStatus != -3 && axStatus != -5));
} }
// Create the unique callsite identifier manually so it can be used // Create the unique callsite identifier manually so it can be used later in
// later in the shouldBePrinted calls. // the shouldBePrinted calls.
msgPrintControlKey keyStatus = msgPrintControlKey( msgPrintControlKey keyStatus = msgPrintControlKey(
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__); pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__);
bool resetCountStatus = true; bool resetCountStatus = true;
@@ -451,9 +433,9 @@ asynStatus turboPmacAxis::doPoll(bool *moving) {
if (pC_->getMsgPrintControl().shouldBePrinted(keyStatus, true, if (pC_->getMsgPrintControl().shouldBePrinted(keyStatus, true,
pC_->pasynUser())) { pC_->pasynUser())) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR, asynPrint(
"Controller \"%s\", axis %d => %s, line " pC_->pasynUser(), ASYN_TRACE_ERROR,
"%d\nEmergency stop " "Controller \"%s\", axis %d => %s, line %d\nEmergency stop "
"activated.%s\n", "activated.%s\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
pC_->getMsgPrintControl().getSuffix()); pC_->getMsgPrintControl().getSuffix());
@@ -526,8 +508,8 @@ asynStatus turboPmacAxis::doPoll(bool *moving) {
*moving = false; *moving = false;
break; break;
case 15: case 15:
// Move is interrupted, but the motor is not ready to receive // Move is interrupted, but the motor is not ready to receive another
// another move command. Therefore it is treated as still moving. // move command. Therefore it is treated as still moving.
*moving = true; *moving = true;
break; break;
case 16: case 16:
@@ -582,10 +564,8 @@ asynStatus turboPmacAxis::doPoll(bool *moving) {
getAxisParamChecked(this, limFromHardware, &limFromHardware); getAxisParamChecked(this, limFromHardware, &limFromHardware);
if (limFromHardware != 0) { if (limFromHardware != 0) {
status = setLimits(highLimit, lowLimit); setAxisParamChecked(this, motorHighLimitFromDriver, highLimit);
if (status != asynSuccess) { setAxisParamChecked(this, motorLowLimitFromDriver, lowLimit);
return status;
}
} }
status = setMotorPosition(currentPosition); status = setMotorPosition(currentPosition);
@@ -624,11 +604,11 @@ asynStatus turboPmacAxis::handleError(int error, char *userMessage,
if (pC_->getMsgPrintControl().shouldBePrinted( if (pC_->getMsgPrintControl().shouldBePrinted(
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, true, pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, true,
pC_->pasynUser())) { pC_->pasynUser())) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR, asynPrint(
pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nAxis is " "Controller \"%s\", axis %d => %s, line %d\nAxis is "
"still moving, but received another move command. EPICS " "still moving, but received another move command. EPICS "
"should prevent this, check if *moving is set " "should prevent this, check if *moving is set correctly.%s\n",
"correctly.%s\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
pC_->getMsgPrintControl().getSuffix()); pC_->getMsgPrintControl().getSuffix());
} }
@@ -640,17 +620,14 @@ asynStatus turboPmacAxis::handleError(int error, char *userMessage,
if (pC_->getMsgPrintControl().shouldBePrinted( if (pC_->getMsgPrintControl().shouldBePrinted(
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, true, pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, true,
pC_->pasynUser())) { pC_->pasynUser())) {
asynPrint( asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
pC_->pasynUser(), ASYN_TRACE_ERROR, "Controller \"%s\", axis %d => %s, line %d\nAir cushion "
"Controller \"%s\", axis %d => %s, line %d\nLost feedback " "feedback stopped during movement (P%2.2d01 = %d).%s\n",
"from auxiliary device during movement (P%2.2d01 = " pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
"%d).%s\n", axisNo_, error, pC_->getMsgPrintControl().getSuffix());
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, axisNo_,
error, pC_->getMsgPrintControl().getSuffix());
} }
snprintf(userMessage, sizeUserMessage, snprintf(userMessage, sizeUserMessage,
"Lost feedback from auxiliary device during movement " "Air cushion feedback stopped during movement (P%2.2d01 = "
"(P%2.2d01 = "
"%d). Please call the support.", "%d). Please call the support.",
axisNo_, error); axisNo_, error);
setAxisParamChecked(this, motorMessageText, userMessage); setAxisParamChecked(this, motorMessageText, userMessage);
@@ -659,27 +636,26 @@ asynStatus turboPmacAxis::handleError(int error, char *userMessage,
if (pC_->getMsgPrintControl().shouldBePrinted( if (pC_->getMsgPrintControl().shouldBePrinted(
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, true, pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, true,
pC_->pasynUser())) { pC_->pasynUser())) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR, asynPrint(
"Controller \"%s\", axis %d => %s, line %d\nNo feedback " pC_->pasynUser(), ASYN_TRACE_ERROR,
"from " "Controller \"%s\", axis %d => %s, line %d\nNo air cushion "
"auxiliary device before movement (P%2.2d01 = %d).%s\n", "feedback before movement start (P%2.2d01 = %d).%s\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, axisNo_,
axisNo_, error, pC_->getMsgPrintControl().getSuffix()); error, pC_->getMsgPrintControl().getSuffix());
} }
snprintf( snprintf(userMessage, sizeUserMessage,
userMessage, sizeUserMessage, "No air cushion feedback before movement start (P%2.2d01 = "
"No feedback from auxiliary device before movement (P%2.2d01 = "
"%d). Please call the support.", "%d). Please call the support.",
axisNo_, error); axisNo_, error);
setAxisParamChecked(this, motorMessageText, userMessage); setAxisParamChecked(this, motorMessageText, userMessage);
break; break;
case 10: case 10:
/* /*
Software limits of the controller have been hit. Since the EPICS Software limits of the controller have been hit. Since the EPICS limits
limits are derived from the software limits and are a little bit are derived from the software limits and are a little bit smaller, this
smaller, this error case can only happen if either the axis has an error case can only happen if either the axis has an incremental encoder
incremental encoder which is not properly homed or if a bug occured. which is not properly homed or if a bug occured.
*/ */
if (pC_->getMsgPrintControl().shouldBePrinted( if (pC_->getMsgPrintControl().shouldBePrinted(
@@ -706,9 +682,9 @@ asynStatus turboPmacAxis::handleError(int error, char *userMessage,
if (pC_->getMsgPrintControl().shouldBePrinted( if (pC_->getMsgPrintControl().shouldBePrinted(
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, true, pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, true,
pC_->pasynUser())) { pC_->pasynUser())) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR, asynPrint(
"Controller \"%s\", axis %d => %s, line %d\nMaximum " pC_->pasynUser(), ASYN_TRACE_ERROR,
"allowed " "Controller \"%s\", axis %d => %s, line %d\nMaximum allowed "
"following error exceeded.%s\n", "following error exceeded.%s\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
pC_->getMsgPrintControl().getSuffix()); pC_->getMsgPrintControl().getSuffix());
@@ -751,9 +727,9 @@ asynStatus turboPmacAxis::handleError(int error, char *userMessage,
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
pC_->getMsgPrintControl().getSuffix()); pC_->getMsgPrintControl().getSuffix());
} }
snprintf(userMessage, sizeUserMessage, snprintf(
"Driver hardware error (P%2.2d01 = 13). Please call the " userMessage, sizeUserMessage,
"support.", "Driver hardware error (P%2.2d01 = 13). Please call the support.",
axisNo_); axisNo_);
setAxisParamChecked(this, motorMessageText, userMessage); setAxisParamChecked(this, motorMessageText, userMessage);
break; break;
@@ -819,9 +795,9 @@ asynStatus turboPmacAxis::doMove(double position, int relative,
getAxisParamChecked(this, motorRecResolution, &motorRecResolution); getAxisParamChecked(this, motorRecResolution, &motorRecResolution);
if (enabled == 0) { if (enabled == 0) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR, asynPrint(
"Controller \"%s\", axis %d => %s, line %d\nAxis is " pC_->pasynUser(), ASYN_TRACE_ERROR,
"disabled.\n", "Controller \"%s\", axis %d => %s, line %d\nAxis is disabled.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__); pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__);
return asynSuccess; return asynSuccess;
} }
@@ -840,8 +816,8 @@ asynStatus turboPmacAxis::doMove(double position, int relative,
getAxisParamChecked(this, motorCanSetSpeed, &motorCanSetSpeed); getAxisParamChecked(this, motorCanSetSpeed, &motorCanSetSpeed);
// Prepend the new motor speed, if the user is allowed to set the speed. // Prepend the new motor speed, if the user is allowed to set the speed.
// Mind the " " (space) before the closing "", as the command created // Mind the " " (space) before the closing "", as the command created here
// here is prepended to the one down below. // is prepended to the one down below.
if (motorCanSetSpeed != 0) { if (motorCanSetSpeed != 0) {
snprintf(command, sizeof(command), "Q%2.2d04=%lf ", axisNo_, snprintf(command, sizeof(command), "Q%2.2d04=%lf ", axisNo_,
motorVelocity); motorVelocity);
@@ -854,8 +830,8 @@ asynStatus turboPmacAxis::doMove(double position, int relative,
motorVelocity); motorVelocity);
} }
// Perform handshake, Set target position (and speed, if allowed) and // Perform handshake, Set target position (and speed, if allowed) and start
// start the move command // the move command
if (relative) { if (relative) {
snprintf(&command[writeOffset], sizeof(command) - writeOffset, snprintf(&command[writeOffset], sizeof(command) - writeOffset,
"P%2.2d23=0 Q%2.2d02=%lf M%2.2d=2", axisNo_, axisNo_, "P%2.2d23=0 Q%2.2d02=%lf M%2.2d=2", axisNo_, axisNo_,
@@ -870,9 +846,9 @@ asynStatus turboPmacAxis::doMove(double position, int relative,
status = pC_->writeRead(axisNo_, command, response, 0); status = pC_->writeRead(axisNo_, command, response, 0);
if (status != asynSuccess) { if (status != asynSuccess) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR, asynPrint(
"Controller \"%s\", axis %d => %s, line %d\nStarting " pC_->pasynUser(), ASYN_TRACE_ERROR,
"movement to " "Controller \"%s\", axis %d => %s, line %d\nStarting movement to "
"target position %lf failed.\n", "target position %lf failed.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
motorTargetPosition); motorTargetPosition);
@@ -880,8 +856,8 @@ asynStatus turboPmacAxis::doMove(double position, int relative,
return status; return status;
} }
// In the next poll, we will check if the handshake has been performed // In the next poll, we will check if the handshake has been performed in a
// in a reasonable time // reasonable time
pTurboPmacA_->waitForHandshake = true; pTurboPmacA_->waitForHandshake = true;
pTurboPmacA_->timeAtHandshake = time(NULL); pTurboPmacA_->timeAtHandshake = time(NULL);
@@ -910,25 +886,25 @@ asynStatus turboPmacAxis::stop(double acceleration) {
status = pC_->writeRead(axisNo_, command, response, 0); status = pC_->writeRead(axisNo_, command, response, 0);
if (status != asynSuccess) { if (status != asynSuccess) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR, asynPrint(
"Controller \"%s\", axis %d => %s, line %d\nStopping " pC_->pasynUser(), ASYN_TRACE_ERROR,
"the movement " "Controller \"%s\", axis %d => %s, line %d\nStopping the movement "
"failed.\n", "failed.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__); pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__);
setAxisParamChecked(this, motorStatusProblem, true); setAxisParamChecked(this, motorStatusProblem, true);
} }
// Reset the driver to idle state and move out of the handshake wait // Reset the driver to idle state and move out of the handshake wait loop,
// loop, if we're currently inside it. // if we're currently inside it.
pTurboPmacA_->waitForHandshake = false; pTurboPmacA_->waitForHandshake = false;
/* /*
Stopping the motor results in a movement and further move commands have Stopping the motor results in a movement and further move commands have to
to wait until the stopping movement is done. Therefore, we need to wait wait until the stopping movement is done. Therefore, we need to wait until
until the poller "sees" the changed state (otherwise, we risk issuing the poller "sees" the changed state (otherwise, we risk issuing move
move commands while the motor is stopping). To ensure that at least one commands while the motor is stopping). To ensure that at least one poll is
poll is done, this thread (which also runs move commands) is paused for done, this thread (which also runs move commands) is paused for twice the
twice the idle poll period. idle poll period.
*/ */
unsigned int idlePollMicros = unsigned int idlePollMicros =
(unsigned int)ceil(pC_->idlePollPeriod() * 1e6); (unsigned int)ceil(pC_->idlePollPeriod() * 1e6);
@@ -959,8 +935,8 @@ asynStatus turboPmacAxis::doReset() {
setAxisParamChecked(this, motorStatusProblem, true); setAxisParamChecked(this, motorStatusProblem, true);
} }
// Reset the driver to idle state and move out of the handshake wait // Reset the driver to idle state and move out of the handshake wait loop,
// loop, if we're currently inside it. // if we're currently inside it.
pTurboPmacA_->waitForHandshake = false; pTurboPmacA_->waitForHandshake = false;
// Disable the axis // Disable the axis
@@ -1058,14 +1034,13 @@ asynStatus turboPmacAxis::readEncoderType() {
} }
/* /*
This is not a command that can always be run when enabling a motor as it This is not a command that can always be run when enabling a motor as it also
also causes relative encoders to reread a position necessitating causes relative encoders to reread a position necessitating recalibration. We
recalibration. We only want it to run on absolute encoders. We also want it only want it to run on absolute encoders. We also want it to be clear to
to be clear to instrument scientists, that power has to be cut to the motor, instrument scientists, that power has to be cut to the motor, in order to reread
in order to reread the encoder as not all motors have breaks and they may the encoder as not all motors have breaks and they may start to move when
start to move when disabled. For that reason, we don't automatically disable disabled. For that reason, we don't automatically disable the motors to run the
the motors to run the command and instead require that the scientists first command and instead require that the scientists first disable the motor.
disable the motor.
*/ */
asynStatus turboPmacAxis::rereadEncoder() { asynStatus turboPmacAxis::rereadEncoder() {
char command[pC_->MAXBUF_] = {0}; char command[pC_->MAXBUF_] = {0};
@@ -1109,12 +1084,11 @@ asynStatus turboPmacAxis::rereadEncoder() {
return asynError; return asynError;
} else { } else {
snprintf(command, sizeof(command), "M%2.2d=15", axisNo_); snprintf(command, sizeof(command), "M%2.2d=15", axisNo_);
asynPrint(pC_->pasynUser(), ASYN_TRACE_FLOW, asynPrint(
"Controller \"%s\", axis %d => %s, line %d\nRereading " pC_->pasynUser(), ASYN_TRACE_FLOW,
"absolute " "Controller \"%s\", axis %d => %s, line %d\nRereading absolute "
"encoder via command %s.\n", "encoder via command %s.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, command);
command);
pC_->writeRead(axisNo_, command, response, 0); pC_->writeRead(axisNo_, command, response, 0);
} }
@@ -1123,8 +1097,8 @@ asynStatus turboPmacAxis::rereadEncoder() {
// it is actually finished, so we instead wait for 0.5 seconds. // it is actually finished, so we instead wait for 0.5 seconds.
usleep(500000); usleep(500000);
// Turn off parameter as finished rereading, this will only be // Turn off parameter as finished rereading, this will only be immediately
// immediately noticed in the read back variable though // noticed in the read back variable though
setAxisParamChecked(this, rereadEncoderPosition, false); setAxisParamChecked(this, rereadEncoderPosition, false);
return asynSuccess; return asynSuccess;
} }
@@ -1144,28 +1118,27 @@ asynStatus turboPmacAxis::enable(bool on) {
/* /*
Continue regardless of the status returned by the poll; we just want to 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 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 function fails before it can determine that, it is assumed that the motor
motor is not moving. is not moving.
*/ */
bool moving = false; bool moving = false;
doPoll(&moving); doPoll(&moving);
// If the axis is currently moving, it cannot be disabled. Ignore the // 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 // command and inform the user. We check the last known status of the axis
// axis instead of "moving", since status -6 is also moving, but the // instead of "moving", since status -6 is also moving, but the motor can
// motor can actually be disabled in this state! // actually be disabled in this state!
int axStatus = pTurboPmacA_->axisStatus; int axStatus = pTurboPmacA_->axisStatus;
if (axStatus == 1 || axStatus == 2 || axStatus == 3 || axStatus == 4 || if (axStatus == 1 || axStatus == 2 || axStatus == 3 || axStatus == 4 ||
axStatus == 5 || axStatus == 6 || axStatus == 7 || axStatus == 8 || axStatus == 5 || axStatus == 6 || axStatus == 7 || axStatus == 8 ||
axStatus == 9 || axStatus == 10 || axStatus == 11 || axStatus == 12 || axStatus == 9 || axStatus == 10 || axStatus == 11 || axStatus == 12 ||
axStatus == 13 || axStatus == 15 || axStatus == 16) { axStatus == 13 || axStatus == 15 || axStatus == 16) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR, asynPrint(
pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nAxis is not " "Controller \"%s\", axis %d => %s, line %d\nAxis is not "
"idle (status %d) and can therefore not be enabled / " "idle (status %d) and can therefore not be enabled / disabled.\n",
"disabled.\n", pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, axStatus);
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
axStatus);
setAxisParamChecked(this, motorMessageText, setAxisParamChecked(this, motorMessageText,
"Axis cannot be disabled while it is moving."); "Axis cannot be disabled while it is moving.");
@@ -1176,9 +1149,9 @@ asynStatus turboPmacAxis::enable(bool on) {
// Axis is already enabled / disabled and a new enable / disable command // Axis is already enabled / disabled and a new enable / disable command
// was sent => Do nothing // was sent => Do nothing
if ((axStatus != -3) == on) { if ((axStatus != -3) == on) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_WARNING, asynPrint(
"Controller \"%s\", axis %d => %s, line %d\nAxis is " pC_->pasynUser(), ASYN_TRACE_WARNING,
"already %s.\n", "Controller \"%s\", axis %d => %s, line %d\nAxis is already %s.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
on ? "enabled" : "disabled"); on ? "enabled" : "disabled");
return asynSuccess; return asynSuccess;
@@ -1329,8 +1302,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/motor/blob/master/motorApp/MotorSrc/asynMotorController.cpp
https://github.com/epics-modules/asyn/blob/master/asyn/asynPortDriver/asynPortDriver.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 The created object is registered in EPICS in its constructor and can safely
safely be "leaked" here. be "leaked" here.
*/ */
#pragma GCC diagnostic ignored "-Wunused-but-set-variable" #pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-variable" #pragma GCC diagnostic ignored "-Wunused-variable"
@@ -1353,15 +1326,14 @@ static const iocshArg *const CreateAxisArgs[] = {&CreateAxisArg0,
static const iocshFuncDef configTurboPmacCreateAxis = { static const iocshFuncDef configTurboPmacCreateAxis = {
"turboPmacAxis", 2, CreateAxisArgs, "turboPmacAxis", 2, CreateAxisArgs,
"Create an instance of a turboPmac axis. The first argument is the " "Create an instance of a turboPmac axis. The first argument is the "
"controller this axis should be attached to, the second argument is " "controller this axis should be attached to, the second argument is the "
"the "
"axis number."}; "axis number."};
static void configTurboPmacCreateAxisCallFunc(const iocshArgBuf *args) { static void configTurboPmacCreateAxisCallFunc(const iocshArgBuf *args) {
turboPmacCreateAxis(args[0].sval, args[1].ival); turboPmacCreateAxis(args[0].sval, args[1].ival);
} }
// This function is made known to EPICS in turboPmac.dbd and is called by // This function is made known to EPICS in turboPmac.dbd and is called by EPICS
// EPICS in order to register both functions in the IOC shell // in order to register both functions in the IOC shell
static void turboPmacAxisRegister(void) { static void turboPmacAxisRegister(void) {
iocshRegister(&configTurboPmacCreateAxis, iocshRegister(&configTurboPmacCreateAxis,
configTurboPmacCreateAxisCallFunc); configTurboPmacCreateAxisCallFunc);

View File

@@ -206,6 +206,7 @@ asynStatus turboPmacController::writeRead(int axisNo, const char *command,
// Definition of local variables. // Definition of local variables.
asynStatus status = asynSuccess; asynStatus status = asynSuccess;
asynStatus timeoutStatus = asynSuccess;
char drvMessageText[MAXBUF_] = {0}; char drvMessageText[MAXBUF_] = {0};
char modResponse[MAXBUF_] = {0}; char modResponse[MAXBUF_] = {0};
int motorStatusProblem = 0; int motorStatusProblem = 0;
@@ -283,7 +284,8 @@ asynStatus turboPmacController::writeRead(int axisNo, const char *command,
getMsgPrintControl().getSuffix()); getMsgPrintControl().getSuffix());
} }
checkComTimeoutWatchdog(axisNo, drvMessageText, sizeof(drvMessageText)); timeoutStatus = checkComTimeoutWatchdog(axisNo, drvMessageText,
sizeof(drvMessageText));
int timeoutCounter = 0; int timeoutCounter = 0;
while (1) { while (1) {
@@ -331,10 +333,14 @@ asynStatus turboPmacController::writeRead(int axisNo, const char *command,
axis->setNeedInit(true); axis->setNeedInit(true);
} }
if (timeoutStatus == asynError) {
status = asynError;
}
// The message should only ever terminate due to reason 2 // The message should only ever terminate due to reason 2
msgPrintControlKey terminateKey = msgPrintControlKey terminateKey =
msgPrintControlKey(portName, axisNo, __PRETTY_FUNCTION__, __LINE__); msgPrintControlKey(portName, axisNo, __PRETTY_FUNCTION__, __LINE__);
if (status == asynSuccess && eomReason != 2) { if (eomReason != 2) {
status = asynError; status = asynError;
char reasonStringified[30] = {0}; char reasonStringified[30] = {0};
@@ -385,7 +391,7 @@ asynStatus turboPmacController::writeRead(int axisNo, const char *command,
} }
msgPrintControlKey numResponsesKey = msgPrintControlKey numResponsesKey =
msgPrintControlKey(portName, axisNo, __PRETTY_FUNCTION__, __LINE__); msgPrintControlKey(portName, axisNo, __PRETTY_FUNCTION__, __LINE__);
if (status == asynSuccess && numExpectedResponses != numReceivedResponses) { if (numExpectedResponses != numReceivedResponses) {
adjustResponseForPrint(modResponse, response, MAXBUF_); adjustResponseForPrint(modResponse, response, MAXBUF_);
if (getMsgPrintControl().shouldBePrinted(numResponsesKey, true, if (getMsgPrintControl().shouldBePrinted(numResponsesKey, true,