Compare commits

...

35 Commits
1.2.1 ... main

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
13 changed files with 278 additions and 184 deletions

View File

@@ -0,0 +1,24 @@
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 Normal file
View File

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

View File

@@ -1,58 +0,0 @@
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

View File

@@ -34,4 +34,5 @@ TEMPLATES += db/turboPmac.db
DBDS += sinqMotor/src/sinqMotor.dbd DBDS += sinqMotor/src/sinqMotor.dbd
DBDS += src/turboPmac.dbd DBDS += src/turboPmac.dbd
USR_CFLAGS += -Wall -Wextra -Weffc++ -Wunused-result -Werror # -Wpedantic // Does not work because EPICS macros trigger warnings 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

View File

@@ -1,18 +1,29 @@
# turboPmac # turboPmac
## <span style="color:red">Please read the documentation of sinqMotor first: https://git.psi.ch/sinq-epics-modules/sinqmotor</span> ## <span style="color:red">Please read the documentation of sinqMotor first: https://gitea.psi.ch/lin-epics-modules/sinqMotor</span>
## Overview ## 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 ## 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://git.psi.ch/sinq-epics-modules/sinqmotor/-/blob/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://gitea.psi.ch/lin-epics-modules/sinqMotor/src/branch/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. The folder "utils" contains utility scripts for working with pmac motor
- writeRead.py: Allows sending commands to and receiving commands from a pmac controller over an ethernet connection. controllers. To read their manual, run the scripts without any arguments.
- 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. - 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 ### IOC startup script
@@ -27,8 +38,10 @@ The full turboPmacX.cmd file looks like this:
epicsEnvSet("DRIVER_PORT","turboPmacX") epicsEnvSet("DRIVER_PORT","turboPmacX")
epicsEnvSet("IP_PORT","p$(DRIVER_PORT)") 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. # Create the TCP/IP socket used to talk with the controller. The socket can be
# We do not use the standard asyn port driver here, but a PMAC-specific one which enables the usage of StreamDevices for # 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. # communicating with the controller directly.
pmacAsynIPPortConfigure("$(IP_PORT)","172.28.101.24:1025") pmacAsynIPPortConfigure("$(IP_PORT)","172.28.101.24:1025")
@@ -48,7 +61,8 @@ turboPmacAxis("$(DRIVER_PORT)",5);
# Set the number of subsequent timeouts # Set the number of subsequent timeouts
setMaxSubsequentTimeouts("$(DRIVER_PORT)", 20); 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); setThresholdComTimeout("$(DRIVER_PORT)", 300, 10);
# Parametrize the EPICS record database with the substitution file named after the MCU. # Parametrize the EPICS record database with the substitution file named after the MCU.
@@ -61,14 +75,22 @@ dbLoadRecords("$(turboPmac_DB)/asynRecord.db","P=$(INSTR)$(DRIVER_PORT),PORT=$(I
### Additional records ### 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 ## Developer guide
### Versioning ### Versioning
Please see the documentation for the module sinqMotor: https://git.psi.ch/sinq-epics-modules/sinqmotor/-/blob/main/README.md. Please see the documentation for the module sinqMotor:
https://gitea.psi.ch/lin-epics-modules/sinqMotor/src/branch/main/README.md.
### How to build it ### 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://git.psi.ch/sinq-epics-modules/sinqmotor/-/blob/main/README.md for more details. This driver can be compiled and installed by running `make install` from the
same directory where the Makefile is located. However, since it uses the git
submodule 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.

BIN
TurboPMAC_manual.pdf Normal file

Binary file not shown.

View File

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

View File

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

View File

@@ -15,6 +15,14 @@
struct turboPmacAxisImpl { struct turboPmacAxisImpl {
bool waitForHandshake; 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; time_t timeAtHandshake;
// The axis status is used when enabling / disabling the motor // The axis status is used when enabling / disabling the motor
int axisStatus; int axisStatus;
@@ -62,11 +70,13 @@ turboPmacAxis::turboPmacAxis(turboPmacController *pC, int axisNo,
axes.push_back(this); axes.push_back(this);
} }
pTurboPmacA_ = std::make_unique<turboPmacAxisImpl>( pTurboPmacA_ = std::make_unique<turboPmacAxisImpl>((turboPmacAxisImpl){
(turboPmacAxisImpl){.waitForHandshake = false, .waitForHandshake = false,
.enableDisable = false,
.timeAtHandshake = 0, .timeAtHandshake = 0,
.axisStatus = 0, .axisStatus = 0,
.needInit = false}); .needInit = false,
});
// Provide initial values for some parameter library entries // Provide initial values for some parameter library entries
status = pC_->setIntegerParam(axisNo_, pC_->rereadEncoderPosition(), 0); status = pC_->setIntegerParam(axisNo_, pC_->rereadEncoderPosition(), 0);
@@ -123,9 +133,16 @@ asynStatus turboPmacAxis::init() {
double motorVelocity = 0.0; double motorVelocity = 0.0;
double motorVmax = 0.0; double motorVmax = 0.0;
double motorAccel = 0.0; double motorAccel = 0.0;
int acoDelay = 0.0; // Offset time for the movement watchdog caused by
// the air cushions in milliseconds. // Conversion factor between engineering units (EU) and encoder counts
int axStatus = 0; 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;
// The parameter library takes some time to be initialized. Therefore we // The parameter library takes some time to be initialized. Therefore we
// wait until the status is not asynParamUndefined anymore. // wait until the status is not asynParamUndefined anymore.
@@ -154,12 +171,14 @@ asynStatus turboPmacAxis::init() {
/* /*
Read out the axis status, the current position, current and maximum speed, Read out the axis status, the current position, current and maximum speed,
acceleration and the air cushion delay. acceleration, the air cushion delay, the scaling factor between counts and
engineering units ([Qxx00] = engineering units / counts) and the deadband
Ixx65.
*/ */
snprintf(command, sizeof(command), snprintf(command, sizeof(command),
"P%2.2d00 Q%2.2d10 Q%2.2d03 Q%2.2d04 Q%2.2d06 P%2.2d22", axisNo_, "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_);
status = pC_->writeRead(axisNo_, command, response, 6); status = pC_->writeRead(axisNo_, command, response, 7);
if (status != asynSuccess) { if (status != asynSuccess) {
asynPrint( asynPrint(
pC_->pasynUser(), ASYN_TRACE_ERROR, pC_->pasynUser(), ASYN_TRACE_ERROR,
@@ -171,8 +190,9 @@ asynStatus turboPmacAxis::init() {
pC_->portName, axisNo(), __PRETTY_FUNCTION__, __LINE__); pC_->portName, axisNo(), __PRETTY_FUNCTION__, __LINE__);
pTurboPmacA_->needInit = true; pTurboPmacA_->needInit = true;
} }
nvals = sscanf(response, "%d %lf %lf %lf %lf %d", &axStatus, &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);
// 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)); setOffsetMovTimeout(std::ceil(acoDelay / 1000.0));
@@ -182,7 +202,7 @@ asynStatus turboPmacAxis::init() {
// here to mm/s^2. // here to mm/s^2.
motorAccel = motorAccel * 1000; motorAccel = motorAccel * 1000;
if (nvals != 6) { if (nvals != 8) {
return pC_->couldNotParseResponse(command, response, axisNo_, return pC_->couldNotParseResponse(command, response, axisNo_,
__PRETTY_FUNCTION__, __LINE__); __PRETTY_FUNCTION__, __LINE__);
} }
@@ -206,6 +226,26 @@ asynStatus turboPmacAxis::init() {
return status; 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 // Update the parameter library immediately
status = callParamCallbacks(); status = callParamCallbacks();
if (status != asynSuccess) { if (status != asynSuccess) {
@@ -253,9 +293,6 @@ asynStatus turboPmacAxis::doPoll(bool *moving) {
double lowLimit = 0.0; double lowLimit = 0.0;
double limitsOffset = 0.0; double limitsOffset = 0.0;
// Was the axis idle during the previous poll?
int previousStatusDone = 1;
// ========================================================================= // =========================================================================
if (pTurboPmacA_->needInit) { if (pTurboPmacA_->needInit) {
@@ -332,8 +369,6 @@ asynStatus turboPmacAxis::doPoll(bool *moving) {
return status; return status;
} }
getAxisParamChecked(this, motorStatusDone, &previousStatusDone);
// Query the axis status // Query the axis status
snprintf(command, sizeof(command), snprintf(command, sizeof(command),
"P%2.2d00 Q%2.2d10 P%2.2d01 Q%2.2d13 Q%2.2d14", axisNo_, axisNo_, "P%2.2d00 Q%2.2d10 P%2.2d01 Q%2.2d13 Q%2.2d14", axisNo_, axisNo_,
@@ -369,9 +404,12 @@ asynStatus turboPmacAxis::doPoll(bool *moving) {
// Store the axis status // Store the axis status
pTurboPmacA_->axisStatus = axStatus; pTurboPmacA_->axisStatus = axStatus;
// Update the enablement PV // Update the enablement PV, if we are not in the middle of a enabling /
// disabling procedure.
if (!(pTurboPmacA_->enableDisable)) {
setAxisParamChecked(this, motorEnableRBV, setAxisParamChecked(this, motorEnableRBV,
(axStatus != -3 && axStatus != -5)); (axStatus != -3 && axStatus != -5));
}
// Create the unique callsite identifier manually so it can be used later in // Create the unique callsite identifier manually so it can be used later in
// the shouldBePrinted calls. // the shouldBePrinted calls.
@@ -514,11 +552,6 @@ asynStatus turboPmacAxis::doPoll(bool *moving) {
errorStatus = handleError(error, userMessage, sizeof(userMessage)); errorStatus = handleError(error, userMessage, sizeof(userMessage));
// Update the parameter library
if (error != 0) {
setAxisParamChecked(this, motorStatusProblem, true);
}
if (*moving == false) { if (*moving == false) {
setAxisParamChecked(this, motorMoveToHome, false); setAxisParamChecked(this, motorMoveToHome, false);
} }
@@ -546,11 +579,6 @@ asynStatus turboPmacAxis::handleError(int error, char *userMessage,
int sizeUserMessage) { int sizeUserMessage) {
asynStatus status = asynError; 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) { switch (error) {
case 0: case 0:
status = asynSuccess; status = asynSuccess;
@@ -559,7 +587,8 @@ asynStatus turboPmacAxis::handleError(int error, char *userMessage,
case 1: case 1:
// EPICS should already prevent this issue in the first place, // EPICS should already prevent this issue in the first place,
// since it contains the user limits // since it contains the user limits
if (pC_->getMsgPrintControl().shouldBePrinted(keyError, true, if (pC_->getMsgPrintControl().shouldBePrinted(
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\nTarget " "Controller \"%s\", axis %d => %s, line %d\nTarget "
@@ -572,7 +601,8 @@ asynStatus turboPmacAxis::handleError(int error, char *userMessage,
break; break;
case 5: case 5:
// Command not possible // Command not possible
if (pC_->getMsgPrintControl().shouldBePrinted(keyError, true, if (pC_->getMsgPrintControl().shouldBePrinted(
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, true,
pC_->pasynUser())) { pC_->pasynUser())) {
asynPrint( asynPrint(
pC_->pasynUser(), ASYN_TRACE_ERROR, pC_->pasynUser(), ASYN_TRACE_ERROR,
@@ -587,7 +617,8 @@ asynStatus turboPmacAxis::handleError(int error, char *userMessage,
"moving. Please call the support."); "moving. Please call the support.");
break; break;
case 8: case 8:
if (pC_->getMsgPrintControl().shouldBePrinted(keyError, true, if (pC_->getMsgPrintControl().shouldBePrinted(
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\nAir cushion " "Controller \"%s\", axis %d => %s, line %d\nAir cushion "
@@ -602,7 +633,8 @@ asynStatus turboPmacAxis::handleError(int error, char *userMessage,
setAxisParamChecked(this, motorMessageText, userMessage); setAxisParamChecked(this, motorMessageText, userMessage);
break; break;
case 9: case 9:
if (pC_->getMsgPrintControl().shouldBePrinted(keyError, true, if (pC_->getMsgPrintControl().shouldBePrinted(
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, true,
pC_->pasynUser())) { pC_->pasynUser())) {
asynPrint( asynPrint(
pC_->pasynUser(), ASYN_TRACE_ERROR, pC_->pasynUser(), ASYN_TRACE_ERROR,
@@ -626,7 +658,8 @@ asynStatus turboPmacAxis::handleError(int error, char *userMessage,
which is not properly homed or if a bug occured. which is not properly homed or if a bug occured.
*/ */
if (pC_->getMsgPrintControl().shouldBePrinted(keyError, true, if (pC_->getMsgPrintControl().shouldBePrinted(
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, true,
pC_->pasynUser())) { pC_->pasynUser())) {
asynPrint( asynPrint(
pC_->pasynUser(), ASYN_TRACE_ERROR, pC_->pasynUser(), ASYN_TRACE_ERROR,
@@ -646,7 +679,8 @@ asynStatus turboPmacAxis::handleError(int error, char *userMessage,
case 11: case 11:
// Following error // Following error
if (pC_->getMsgPrintControl().shouldBePrinted(keyError, true, if (pC_->getMsgPrintControl().shouldBePrinted(
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, true,
pC_->pasynUser())) { pC_->pasynUser())) {
asynPrint( asynPrint(
pC_->pasynUser(), ASYN_TRACE_ERROR, pC_->pasynUser(), ASYN_TRACE_ERROR,
@@ -664,7 +698,8 @@ asynStatus turboPmacAxis::handleError(int error, char *userMessage,
break; break;
case 12: case 12:
if (pC_->getMsgPrintControl().shouldBePrinted(keyError, true, if (pC_->getMsgPrintControl().shouldBePrinted(
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\nSecurity " "Controller \"%s\", axis %d => %s, line %d\nSecurity "
@@ -683,7 +718,8 @@ asynStatus turboPmacAxis::handleError(int error, char *userMessage,
case 13: case 13:
// Driver hardware error triggered // Driver hardware error triggered
if (pC_->getMsgPrintControl().shouldBePrinted(keyError, true, if (pC_->getMsgPrintControl().shouldBePrinted(
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\nDriver " "Controller \"%s\", axis %d => %s, line %d\nDriver "
@@ -700,7 +736,8 @@ asynStatus turboPmacAxis::handleError(int error, char *userMessage,
case 14: case 14:
// EPICS should already prevent this issue in the first place, // EPICS should already prevent this issue in the first place,
// since it contains the user limits // since it contains the user limits
if (pC_->getMsgPrintControl().shouldBePrinted(keyError, true, if (pC_->getMsgPrintControl().shouldBePrinted(
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\nMove " "Controller \"%s\", axis %d => %s, line %d\nMove "
@@ -716,7 +753,8 @@ asynStatus turboPmacAxis::handleError(int error, char *userMessage,
break; break;
default: default:
if (pC_->getMsgPrintControl().shouldBePrinted(keyError, true, if (pC_->getMsgPrintControl().shouldBePrinted(
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, true,
pC_->pasynUser())) { pC_->pasynUser())) {
asynPrint( asynPrint(
pC_->pasynUser(), ASYN_TRACE_ERROR, pC_->pasynUser(), ASYN_TRACE_ERROR,
@@ -728,10 +766,6 @@ asynStatus turboPmacAxis::handleError(int error, char *userMessage,
setAxisParamChecked(this, motorMessageText, userMessage); setAxisParamChecked(this, motorMessageText, userMessage);
break; break;
} }
if (status == asynSuccess) {
pC_->getMsgPrintControl().resetCount(keyError, pC_->pasynUser());
}
return status; return status;
} }
@@ -744,7 +778,7 @@ asynStatus turboPmacAxis::doMove(double position, int relative,
char command[pC_->MAXBUF_] = {0}; char command[pC_->MAXBUF_] = {0};
char response[pC_->MAXBUF_] = {0}; char response[pC_->MAXBUF_] = {0};
double motorCoordinatesPosition = 0.0; double motorTargetPosition = 0.0;
double motorRecResolution = 0.0; double motorRecResolution = 0.0;
double motorVelocity = 0.0; double motorVelocity = 0.0;
int enabled = 0; int enabled = 0;
@@ -753,6 +787,10 @@ asynStatus turboPmacAxis::doMove(double position, int relative,
// ========================================================================= // =========================================================================
// Suppress unused variables warning
(void)minVelocity;
(void)acceleration;
getAxisParamChecked(this, motorEnableRBV, &enabled); getAxisParamChecked(this, motorEnableRBV, &enabled);
getAxisParamChecked(this, motorRecResolution, &motorRecResolution); getAxisParamChecked(this, motorRecResolution, &motorRecResolution);
@@ -765,13 +803,14 @@ asynStatus turboPmacAxis::doMove(double position, int relative,
} }
// Convert from EPICS to user / motor units // Convert from EPICS to user / motor units
motorCoordinatesPosition = position * motorRecResolution; motorTargetPosition = position * motorRecResolution;
motorVelocity = maxVelocity * motorRecResolution; motorVelocity = maxVelocity * motorRecResolution;
asynPrint(pC_->pasynUser(), ASYN_TRACE_FLOW, asynPrint(pC_->pasynUser(), ASYN_TRACE_FLOW,
"Controller \"%s\", axis %d => %s, line %d\nStart of axis to " "Controller \"%s\", axis %d => %s, line %d\nStart of axis to "
"position %lf.\n", "position %lf.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, position); pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
motorTargetPosition);
// Check if the speed is allowed to be changed // Check if the speed is allowed to be changed
getAxisParamChecked(this, motorCanSetSpeed, &motorCanSetSpeed); getAxisParamChecked(this, motorCanSetSpeed, &motorCanSetSpeed);
@@ -796,11 +835,11 @@ asynStatus turboPmacAxis::doMove(double position, int relative,
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_,
motorCoordinatesPosition, axisNo_); motorTargetPosition, axisNo_);
} else { } else {
snprintf(&command[writeOffset], sizeof(command) - writeOffset, snprintf(&command[writeOffset], sizeof(command) - writeOffset,
"P%2.2d23=0 Q%2.2d01=%lf M%2.2d=1", axisNo_, axisNo_, "P%2.2d23=0 Q%2.2d01=%lf M%2.2d=1", axisNo_, axisNo_,
motorCoordinatesPosition, axisNo_); motorTargetPosition, axisNo_);
} }
// We don't expect an answer // We don't expect an answer
@@ -812,7 +851,7 @@ asynStatus turboPmacAxis::doMove(double position, int relative,
"Controller \"%s\", axis %d => %s, line %d\nStarting 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__,
motorCoordinatesPosition); motorTargetPosition);
setAxisParamChecked(this, motorStatusProblem, true); setAxisParamChecked(this, motorStatusProblem, true);
return status; return status;
} }
@@ -833,7 +872,6 @@ asynStatus turboPmacAxis::doMove(double position, int relative,
asynStatus turboPmacAxis::stop(double acceleration) { asynStatus turboPmacAxis::stop(double acceleration) {
// Status of read-write-operations of ASCII commands to the controller
asynStatus status = asynSuccess; asynStatus status = asynSuccess;
char command[pC_->MAXBUF_] = {0}; char command[pC_->MAXBUF_] = {0};
@@ -841,6 +879,9 @@ asynStatus turboPmacAxis::stop(double acceleration) {
// ========================================================================= // =========================================================================
// Suppress unused variables warning
(void)acceleration;
snprintf(command, sizeof(command), "M%2.2d=8", axisNo_); snprintf(command, sizeof(command), "M%2.2d=8", axisNo_);
status = pC_->writeRead(axisNo_, command, response, 0); status = pC_->writeRead(axisNo_, command, response, 0);
@@ -857,6 +898,18 @@ asynStatus turboPmacAxis::stop(double acceleration) {
// 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 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; return status;
} }
@@ -886,13 +939,14 @@ asynStatus turboPmacAxis::doReset() {
// if we're currently inside it. // if we're currently inside it.
pTurboPmacA_->waitForHandshake = false; pTurboPmacA_->waitForHandshake = false;
return status; // Disable the axis
return enable(false);
} }
/* /*
Home the axis. On absolute encoder systems, this is a no-op Home the axis. On absolute encoder systems, this is a no-op
*/ */
asynStatus turboPmacAxis::doHome(double min_velocity, double max_velocity, asynStatus turboPmacAxis::doHome(double minVelocity, double maxVelocity,
double acceleration, int forwards) { double acceleration, int forwards) {
// Status of read-write-operations of ASCII commands to the controller // Status of read-write-operations of ASCII commands to the controller
@@ -903,6 +957,12 @@ asynStatus turboPmacAxis::doHome(double min_velocity, double max_velocity,
// ========================================================================= // =========================================================================
// Suppress unused variables warning
(void)minVelocity;
(void)maxVelocity;
(void)acceleration;
(void)forwards;
getAxisParamChecked(this, encoderType, &response); getAxisParamChecked(this, encoderType, &response);
// Only send the home command if the axis has an incremental encoder // Only send the home command if the axis has an incremental encoder
@@ -1045,7 +1105,7 @@ asynStatus turboPmacAxis::rereadEncoder() {
asynStatus turboPmacAxis::enable(bool on) { asynStatus turboPmacAxis::enable(bool on) {
int timeout_enable_disable = 2; int timeout_enable_disable = 5;
char command[pC_->MAXBUF_] = {0}; char command[pC_->MAXBUF_] = {0};
char response[pC_->MAXBUF_] = {0}; char response[pC_->MAXBUF_] = {0};
int nvals = 0; int nvals = 0;
@@ -1073,13 +1133,16 @@ asynStatus turboPmacAxis::enable(bool on) {
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 and can therefore not be enabled / disabled.\n", "idle (status %d) and can therefore not be enabled / disabled.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__); 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.");
pTurboPmacA_->enableDisable = false;
return asynError; return asynError;
} }
@@ -1102,7 +1165,10 @@ asynStatus turboPmacAxis::enable(bool on) {
} }
} }
// Enable / disable the axis if it is not moving // Now the actual enabling / disabling starts
pTurboPmacA_->enableDisable = true;
// Enable / disable the axis
snprintf(command, sizeof(command), "M%2.2d14=%d", axisNo_, on); snprintf(command, sizeof(command), "M%2.2d14=%d", axisNo_, on);
asynPrint(pC_->pasynUser(), ASYN_TRACE_FLOW, asynPrint(pC_->pasynUser(), ASYN_TRACE_FLOW,
"Controller \"%s\", axis %d => %s, line %d\n%s axis\n", "Controller \"%s\", axis %d => %s, line %d\n%s axis\n",
@@ -1120,10 +1186,14 @@ asynStatus turboPmacAxis::enable(bool on) {
int startTime = time(NULL); int startTime = time(NULL);
while (time(NULL) < startTime + timeout_enable_disable) { while (time(NULL) < startTime + timeout_enable_disable) {
// Wait a bit between status updates from the controller.
usleep(10000);
// Read the axis status // Read the axis status
usleep(100000);
status = pC_->writeRead(axisNo_, command, response, 1); status = pC_->writeRead(axisNo_, command, response, 1);
if (status != asynSuccess) { if (status != asynSuccess) {
// Enabling / disabling procedure failed
pTurboPmacA_->enableDisable = false;
return status; return status;
} }
nvals = sscanf(response, "%d", &pTurboPmacA_->axisStatus); nvals = sscanf(response, "%d", &pTurboPmacA_->axisStatus);
@@ -1133,9 +1203,13 @@ asynStatus turboPmacAxis::enable(bool on) {
} }
if ((pTurboPmacA_->axisStatus != -3) == on) { if ((pTurboPmacA_->axisStatus != -3) == on) {
usleep(500000);
bool moving = false; bool moving = false;
// Perform a poll to update the parameter library forcedPoll(&moving);
poll(&moving);
// Enabling / disabling procedure is completed (successfully)
pTurboPmacA_->enableDisable = false;
return asynSuccess; return asynSuccess;
} }
} }
@@ -1152,6 +1226,9 @@ asynStatus turboPmacAxis::enable(bool on) {
snprintf(command, sizeof(command), "Failed to %s within %d seconds", snprintf(command, sizeof(command), "Failed to %s within %d seconds",
on ? "enable" : "disable", timeout_enable_disable); on ? "enable" : "disable", timeout_enable_disable);
setAxisParamChecked(this, motorMessageText, command); setAxisParamChecked(this, motorMessageText, command);
// Enabling / disabling procedure failed
pTurboPmacA_->enableDisable = false;
return asynError; return asynError;
} }

View File

@@ -4,15 +4,19 @@
#include "turboPmacController.h" #include "turboPmacController.h"
#include <memory> #include <memory>
struct turboPmacAxisImpl; struct HIDDEN turboPmacAxisImpl;
class turboPmacAxis : public sinqAxis { class HIDDEN turboPmacAxis : public sinqAxis {
public: public:
/** /**
* @brief Construct a new turboPmacAxis * @brief Construct a new turboPmacAxis
* *
* @param pController Pointer to the associated controller * @param pController Pointer to the associated controller
* @param axisNo Index of the axis * @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, turboPmacAxis(turboPmacController *pController, int axisNo,
bool initialize = true); bool initialize = true);
@@ -24,6 +28,18 @@ class turboPmacAxis : public sinqAxis {
*/ */
virtual ~turboPmacAxis(); 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 * @brief Implementation of the `stop` function from asynMotorAxis
* *
@@ -70,18 +86,6 @@ class turboPmacAxis : public sinqAxis {
double min_velocity, double max_velocity, double min_velocity, double max_velocity,
double acceleration); 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. * @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. // User for writing int32 values to the port driver.
asynUser *pasynInt32SyncIOipPort; asynUser *pasynInt32SyncIOipPort;
// Indices of additional PVs // Indices of additional ParamLib entries
int rereadEncoderPosition; int rereadEncoderPosition;
int readConfig; int readConfig;
int flushHardware; int flushHardware;
int limFromHardware; int limFromHardware;
}; };
#define NUM_turboPmac_DRIVER_PARAMS 3 #define NUM_turboPmac_DRIVER_PARAMS 5
turboPmacController::turboPmacController(const char *portName, turboPmacController::turboPmacController(const char *portName,
const char *ipPortConfigName, const char *ipPortConfigName,
@@ -61,17 +61,17 @@ turboPmacController::turboPmacController(const char *portName,
- REREAD_ENCODER_POSITION - REREAD_ENCODER_POSITION
- READ_CONFIG - READ_CONFIG
*/ */
numExtraParams + NUM_turboPmac_DRIVER_PARAMS) numExtraParams + NUM_turboPmac_DRIVER_PARAMS),
pTurboPmacC_(
{
// The paramLib indices are populated with the calls to createParam
pTurboPmacC_ =
std::make_unique<turboPmacControllerImpl>((turboPmacControllerImpl){ std::make_unique<turboPmacControllerImpl>((turboPmacControllerImpl){
.comTimeout = comTimeout, .comTimeout = comTimeout,
.lastResponse = {0}, .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 // Initialization of local variables
asynStatus status = asynSuccess; asynStatus status = asynSuccess;
@@ -207,7 +207,6 @@ 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; asynStatus timeoutStatus = asynSuccess;
// char fullCommand[MAXBUF_] = {0};
char drvMessageText[MAXBUF_] = {0}; char drvMessageText[MAXBUF_] = {0};
char modResponse[MAXBUF_] = {0}; char modResponse[MAXBUF_] = {0};
int motorStatusProblem = 0; int motorStatusProblem = 0;
@@ -255,6 +254,22 @@ asynStatus turboPmacController::writeRead(int axisNo, const char *command,
pasynOctetSyncIOipPort(), command, commandLength, response, MAXBUF_, pasynOctetSyncIOipPort(), command, commandLength, response, MAXBUF_,
pTurboPmacC_->comTimeout, &nbytesOut, &nbytesIn, &eomReason); 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 comKey =
msgPrintControlKey(portName, axisNo, __PRETTY_FUNCTION__, __LINE__); msgPrintControlKey(portName, axisNo, __PRETTY_FUNCTION__, __LINE__);
@@ -558,7 +573,8 @@ static const iocshArg *const CreateControllerArgs[] = {
&CreateControllerArg0, &CreateControllerArg1, &CreateControllerArg2, &CreateControllerArg0, &CreateControllerArg1, &CreateControllerArg2,
&CreateControllerArg3, &CreateControllerArg4, &CreateControllerArg5}; &CreateControllerArg3, &CreateControllerArg4, &CreateControllerArg5};
static const iocshFuncDef configTurboPmacCreateController = { static const iocshFuncDef configTurboPmacCreateController = {
"turboPmacController", 6, CreateControllerArgs}; "turboPmacController", 6, CreateControllerArgs,
"Create a new instance of a TurboPMAC controller."};
static void configTurboPmacCreateControllerCallFunc(const iocshArgBuf *args) { static void configTurboPmacCreateControllerCallFunc(const iocshArgBuf *args) {
turboPmacCreateController(args[0].sval, args[1].sval, args[2].ival, turboPmacCreateController(args[0].sval, args[1].sval, args[2].ival,
args[3].dval, args[4].dval, args[5].dval); 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 // Forward declaration of the controller class to resolve the cyclic dependency
// between the controller and the axis .h-file. See // between the controller and the axis .h-file. See
// https://en.cppreference.com/w/cpp/language/class. // https://en.cppreference.com/w/cpp/language/class.
class turboPmacAxis; class HIDDEN turboPmacAxis;
struct turboPmacControllerImpl; struct HIDDEN turboPmacControllerImpl;
class turboPmacController : public sinqController { class HIDDEN turboPmacController : public sinqController {
public: public:
/** /**
* @brief Construct a new turboPmacController object. This function is meant * @brief Construct a new turboPmacController object. This function is meant
@@ -93,7 +93,8 @@ class turboPmacController : public sinqController {
* @return asynStatus * @return asynStatus
*/ */
asynStatus writeRead(int axisNo, const char *command, char *response, asynStatus writeRead(int axisNo, const char *command, char *response,
int numExpectedResponses); int numExpectedResponses)
__attribute__((visibility("hidden")));
/** /**
* @brief Specialized version of sinqController::couldNotParseResponse * @brief Specialized version of sinqController::couldNotParseResponse