Applied various bugfixes to make this driver fully operational

This commit is contained in:
2025-02-14 16:31:23 +01:00
parent fd4467ae54
commit b4e49a9d7a
6 changed files with 597 additions and 409 deletions

View File

@ -10,8 +10,11 @@ ARCH_FILTER=RHEL%
REQUIRED+=asynMotor REQUIRED+=asynMotor
REQUIRED+=sinqMotor REQUIRED+=sinqMotor
# Specify the version of asynMotor we want to build against
asynMotor_VERSION=7.2.2
# Specify the version of sinqMotor we want to build against # Specify the version of sinqMotor we want to build against
sinqMotor_VERSION=0.6.3 sinqMotor_VERSION=0.7.0
# These headers allow to depend on this library for derived drivers. # These headers allow to depend on this library for derived drivers.
HEADERS += src/turboPmacAxis.h HEADERS += src/turboPmacAxis.h
@ -27,4 +30,4 @@ TEMPLATES += db/turboPmac.db
# This file registers the motor-specific functions in the IOC shell. # This file registers the motor-specific functions in the IOC shell.
DBDS += src/turboPmac.dbd DBDS += src/turboPmac.dbd
USR_CFLAGS += -Wall -Wextra -Weffc++ -Wunused-result # -Werror USR_CFLAGS += -Wall -Wextra -Weffc++ -Wunused-result -Wextra -Werror # -Wpedantic // Does not work because EPICS macros trigger warnings

View File

@ -1,4 +1,5 @@
#--------------------------------------------- #---------------------------------------------
# SINQ specific DB definitions # SINQ specific DB definitions
#--------------------------------------------- #---------------------------------------------
registrar(turboPmacRegister) registrar(turboPmacControllerRegister)
registrar(turboPmacAxisRegister)

File diff suppressed because it is too large Load Diff

View File

@ -69,7 +69,7 @@ class turboPmacAxis : public sinqAxis {
double max_velocity, double acceleration); double max_velocity, double acceleration);
/** /**
* @brief Implementation of the `atFirstPoll` function from sinqAxis. * @brief Readout of some values from the controller at IOC startup
* *
* The following steps are performed: * The following steps are performed:
* - Read out the motor status, motor position, velocity and acceleration * - Read out the motor status, motor position, velocity and acceleration
@ -78,7 +78,7 @@ class turboPmacAxis : public sinqAxis {
* *
* @return asynStatus * @return asynStatus
*/ */
asynStatus atFirstPoll(); asynStatus init();
/** /**
* @brief Enable / disable the axis. * @brief Enable / disable the axis.
@ -106,7 +106,6 @@ class turboPmacAxis : public sinqAxis {
protected: protected:
turboPmacController *pC_; turboPmacController *pC_;
bool initial_poll_;
bool waitForHandshake_; bool waitForHandshake_;
time_t timeAtHandshake_; time_t timeAtHandshake_;

View File

@ -4,6 +4,7 @@
#include "turboPmacAxis.h" #include "turboPmacAxis.h"
#include <epicsExport.h> #include <epicsExport.h>
#include <errlog.h> #include <errlog.h>
#include <initHooks.h>
#include <iocsh.h> #include <iocsh.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <registryFunction.h> #include <registryFunction.h>
@ -63,6 +64,10 @@ turboPmacController::turboPmacController(const char *portName,
lowLevelPortUser_ = nullptr; lowLevelPortUser_ = nullptr;
comTimeout_ = comTimeout; comTimeout_ = comTimeout;
// Maximum allowed number of subsequent timeouts before the user is
// informed.
maxSubsequentTimeouts_ = 10;
// =========================================================================; // =========================================================================;
/* /*
@ -71,10 +76,9 @@ turboPmacController::turboPmacController(const char *portName,
*/ */
pasynOctetSyncIO->connect(ipPortConfigName, 0, &lowLevelPortUser_, NULL); pasynOctetSyncIO->connect(ipPortConfigName, 0, &lowLevelPortUser_, NULL);
if (status != asynSuccess || lowLevelPortUser_ == nullptr) { if (status != asynSuccess || lowLevelPortUser_ == nullptr) {
errlogPrintf( errlogPrintf("Controller \"%s\" => %s, line %d\nFATAL ERROR "
"%s => line %d:\nFATAL ERROR (cannot connect to MCU controller).\n" "(cannot connect to MCU controller).\nTerminating IOC",
"Terminating IOC", portName, __PRETTY_FUNCTION__, __LINE__);
__PRETTY_FUNCTION__, __LINE__);
exit(-1); exit(-1);
} }
@ -85,18 +89,20 @@ turboPmacController::turboPmacController(const char *portName,
&rereadEncoderPosition_); &rereadEncoderPosition_);
if (status != asynSuccess) { if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (creating a parameter failed " "Controller \"%s\" => %s, line %d\nFATAL ERROR (creating a "
"with %s).\nTerminating IOC", "parameter failed with %s).\nTerminating IOC",
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status)); portName, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(status));
exit(-1); exit(-1);
} }
status = createParam("READ_CONFIG", asynParamInt32, &readConfig_); status = createParam("READ_CONFIG", asynParamInt32, &readConfig_);
if (status != asynSuccess) { if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (creating a parameter failed " "Controller \"%s\" => %s, line %d\nFATAL ERROR (creating a "
"with %s).\nTerminating IOC", "parameter failed with %s).\nTerminating IOC",
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status)); portName, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(status));
exit(-1); exit(-1);
} }
@ -113,20 +119,22 @@ turboPmacController::turboPmacController(const char *portName,
lowLevelPortUser_, message_from_device, strlen(message_from_device)); lowLevelPortUser_, message_from_device, strlen(message_from_device));
if (status != asynSuccess) { if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (setting input EOS failed " "Controller \"%s\" => %s, line %d\nFATAL ERROR "
"with %s).\nTerminating IOC", "(setting input EOS failed with %s).\nTerminating IOC",
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status)); portName, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(status));
pasynOctetSyncIO->disconnect(lowLevelPortUser_); pasynOctetSyncIO->disconnect(lowLevelPortUser_);
exit(-1); exit(-1);
} }
status = callParamCallbacks(); status = callParamCallbacks();
if (status != asynSuccess) { if (status != asynSuccess) {
asynPrint( asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
this->pasynUserSelf, ASYN_TRACE_ERROR, "Controller \"%s\" => %s, line %d\nFATAL ERROR "
"%s => line %d:\nFATAL ERROR (executing ParamLib callbacks failed " "(executing ParamLib callbacks failed "
"with %s).\nTerminating IOC", "with %s).\nTerminating IOC",
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status)); portName, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(status));
pasynOctetSyncIO->disconnect(lowLevelPortUser_); pasynOctetSyncIO->disconnect(lowLevelPortUser_);
exit(-1); exit(-1);
} }
@ -165,8 +173,9 @@ turboPmacAxis *turboPmacController::castToAxis(asynMotorAxis *asynAxis) {
turboPmacAxis *axis = dynamic_cast<turboPmacAxis *>(asynAxis); turboPmacAxis *axis = dynamic_cast<turboPmacAxis *>(asynAxis);
if (axis == nullptr) { if (axis == nullptr) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nAxis %d is not an instance of turboPmacAxis", "Controller \"%s\", axis %d => %s, line %d\nAxis is not "
__PRETTY_FUNCTION__, __LINE__, axis->axisNo_); "an instance of turboPmacAxis",
portName, axis->axisNo_, __PRETTY_FUNCTION__, __LINE__);
} }
return axis; return axis;
} }
@ -177,7 +186,8 @@ asynStatus turboPmacController::writeRead(int axisNo, const char *command,
// Definition of local variables. // Definition of local variables.
asynStatus status = asynSuccess; asynStatus status = asynSuccess;
asynStatus pl_status = asynSuccess; asynStatus paramLibStatus = asynSuccess;
asynStatus timeoutStatus = asynSuccess;
char fullCommand[MAXBUF_] = {0}; char fullCommand[MAXBUF_] = {0};
char drvMessageText[MAXBUF_] = {0}; char drvMessageText[MAXBUF_] = {0};
char modResponse[MAXBUF_] = {0}; char modResponse[MAXBUF_] = {0};
@ -226,6 +236,11 @@ asynStatus turboPmacController::writeRead(int axisNo, const char *command,
value [Actual message] It is not necessary to append a terminator, since value [Actual message] It is not necessary to append a terminator, since
this protocol encodes the message length at the beginning. See Turbo PMAC this protocol encodes the message length at the beginning. See Turbo PMAC
User Manual, page 418 in VR_PMAC_GETRESPONSE User Manual, page 418 in VR_PMAC_GETRESPONSE
x0D (ASCII value of carriage return) -> The controller needs a carriage
return at the end of a "send" command (a command were we transmit data via
=). For "request" commands (e.g. read status or position), this is not
necessary, but it doesn't hurt either, therefore we always add a carriage
return.
The message has to be build manually into the buffer fullCommand, since it The message has to be build manually into the buffer fullCommand, since it
contains NULL terminators in its middle, therefore the string manipulation contains NULL terminators in its middle, therefore the string manipulation
@ -242,46 +257,25 @@ asynStatus turboPmacController::writeRead(int axisNo, const char *command,
// The size of size_t is platform dependant (pointers-sized), while htons // The size of size_t is platform dependant (pointers-sized), while htons
// needs an unsigned int. The byte order is then converted from host to // needs an unsigned int. The byte order is then converted from host to
// network order. // network order. The offset "+1" is for the carriage return.
u_int16_t len = htons(static_cast<u_int16_t>(commandLength)); u_int16_t len = htons(static_cast<u_int16_t>(commandLength + 1));
// Split up into the upper and the lower byte // Split up into the upper and the lower byte
fullCommand[7] = (char)(len >> 8); // Shift the 8 higher bits to the right fullCommand[7] = (char)(len >> 8); // Shift the 8 higher bits to the right
fullCommand[8] = (char)(len & 0xFF); // Mask the higher bits fullCommand[8] = (char)(len & 0xFF); // Mask the higher bits
// Write the actual command behind the protocol // Write the actual command behind the protocol
for (int i = 0; i < commandLength; i++) { for (size_t i = 0; i < commandLength; i++) {
fullCommand[i + offset] = command[i]; fullCommand[i + offset] = command[i];
} }
fullCommand[offset + commandLength] = '\x0D';
// +1 for the carriage return.
const size_t fullComandLength = offset + commandLength + 1;
asynPrint(this->pasynUserSelf, ASYN_TRACEIO_DRIVER, asynPrint(this->pasynUserSelf, ASYN_TRACEIO_DRIVER,
"%s => line %d:\nSending command %s", __PRETTY_FUNCTION__, "Controller \"%s\", axis %d => %s, line %d\nSending command %s",
__LINE__, fullCommand); portName, axisNo, __PRETTY_FUNCTION__, __LINE__, fullCommand);
// TEST CODE
/*
Check if there is data on the MCU which is waiting to be read
TBD: Flush if there is something?
*/
u_int16_t val = htons(2);
char readReadyCommand[MAXBUF_] = {0};
char readReadyResponse[MAXBUF_] = {0};
readReadyCommand[0] = '\xC0';
readReadyCommand[1] = '\xC2';
readReadyCommand[4] = (char)(val >> 8);
readReadyCommand[5] = (char)(val & 0xFF);
status = pasynOctetSyncIO->write(lowLevelPortUser_, readReadyCommand, 6,
comTimeout_, &nbytesOut);
status =
pasynOctetSyncIO->read(lowLevelPortUser_, readReadyResponse, MAXBUF_,
comTimeout_, &nbytesIn, &eomReason);
if (readReadyResponse[1] != 0) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nBytes: %x %x\n", __PRETTY_FUNCTION__,
__LINE__, readReadyResponse[0], readReadyResponse[1]);
}
/* /*
We use separated write and read commands here, not the combined writeRead We use separated write and read commands here, not the combined writeRead
@ -291,60 +285,105 @@ asynStatus turboPmacController::writeRead(int axisNo, const char *command,
The flush itself reads repeatedly from the MCU until no messages are there The flush itself reads repeatedly from the MCU until no messages are there
anymore. (The Diamond Light Source driver first send a PMAC flush command anymore. (The Diamond Light Source driver first send a PMAC flush command
and then does the same as the asyn flush). We don't want this behaviour. and then does the same as the asyn flush). We don't want this behaviour.
(https://www.slac.stanford.edu/grp/lcls/controls/global/doc/epics-modules/R3-14-12/asyn/asyn-R4-18-lcls2/asyn/interfaces/asynOctetBase.c): (https://www.slac.stanford.edu/grp/lcls/controls/global/doc/epics-modules/R3-14-12/asyn/asyn-R4-18-lcls2/asyn/interfaces/asynOctetBase.c)
If a timeout occurs during writing or reading, inform the user that we're
trying to reconnect. If the problem persists, ask them to call the support
*/ */
// Send out the command
status = pasynOctetSyncIO->write(lowLevelPortUser_, fullCommand, status = pasynOctetSyncIO->write(lowLevelPortUser_, fullCommand,
commandLength + offset, comTimeout_, fullComandLength, comTimeout_, &nbytesOut);
&nbytesOut);
if (status != asynSuccess) { if (status == asynTimeout) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nError %s while writing to the controller\n", "Controller \"%s\", axis %d => %s, line %d\nTimeout while "
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status)); "writing to the MCU\n",
pl_status = setStringParam( portName, axisNo, __PRETTY_FUNCTION__, __LINE__);
motorMessageText_, "Communication timeout between IOC and "
"motor controller. Please call the support."); timeoutStatus = checkComTimeoutWatchdog(axisNo, drvMessageText,
if (pl_status != asynSuccess) { sizeof(drvMessageText));
return paramLibAccessFailed(pl_status, "motorMessageText_",
__PRETTY_FUNCTION__, __LINE__); int timeoutCounter = 0;
while (1) {
checkMaxSubsequentTimeouts(timeoutCounter, axis);
timeoutCounter += 1;
status = pasynOctetSyncIO->write(lowLevelPortUser_, fullCommand,
fullComandLength, comTimeout_,
&nbytesOut);
if (status != asynTimeout) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line "
"%d\nReconnected after write timeout\n",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__);
break;
}
} }
} else if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nError %s while "
"writing to the controller\n",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(status));
} }
// Read the response from the MCU buffer // Read the response from the MCU buffer
status = pasynOctetSyncIO->read(lowLevelPortUser_, response, MAXBUF_, status = pasynOctetSyncIO->read(lowLevelPortUser_, response, MAXBUF_,
comTimeout_, &nbytesIn, &eomReason); comTimeout_, &nbytesIn, &eomReason);
if (status != asynSuccess) { if (status == asynTimeout) {
asynPrint(
this->pasynUserSelf, ASYN_TRACE_ERROR, asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nError %s while reading from the controller\n ", "Controller \"%s\", axis %d => %s, line %d\nTimeout while "
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status)); "reading from the MCU\n",
pl_status = setStringParam( portName, axisNo, __PRETTY_FUNCTION__, __LINE__);
motorMessageText_, "Communication timeout between IOC and "
"motor controller. Please call the support."); // Add this event to the back of the timeout event counter
if (pl_status != asynSuccess) { timeoutStatus = checkComTimeoutWatchdog(axisNo, drvMessageText,
return paramLibAccessFailed(pl_status, "motorMessageText_", sizeof(drvMessageText));
__PRETTY_FUNCTION__, __LINE__);
int timeoutCounter = 0;
while (1) {
checkMaxSubsequentTimeouts(timeoutCounter, axis);
timeoutCounter += 1;
status =
pasynOctetSyncIO->read(lowLevelPortUser_, response, MAXBUF_,
comTimeout_, &nbytesIn, &eomReason);
if (status != asynTimeout) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line "
"%d\nReconnected after read timeout\n",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__);
break;
}
} }
} else if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nError %s while "
"reading from the controller\n",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(status));
} }
// // TEST CODE if (timeoutStatus == asynError) {
// if (response == lastResponse) { status = asynError;
// asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, }
// "%s => line %d:\nBytes: %x %x\n", __PRETTY_FUNCTION__,
// __LINE__, readReadyResponse[0], readReadyResponse[1]);
// } else {
// for (int i = 0; i < MAXBUF_; i++) {
// lastResponse[i] = response[i];
// }
// }
// TBD: The message should only ever terminate due to reason 2 -> Should we // The message should only ever terminate due to reason 2
// indicate an error otherwise? if (eomReason != 2) {
asynPrint(this->pasynUserSelf, ASYN_TRACEIO_DRIVER, status = asynError;
"%s => line %d:\nMessage terminated due to reason %i\n",
__PRETTY_FUNCTION__, __LINE__, eomReason); snprintf(drvMessageText, sizeof(drvMessageText),
"Terminated message due to reason %d (should be 2).",
eomReason);
asynPrint(this->pasynUserSelf, ASYN_TRACEIO_DRIVER,
"Controller \"%s\", axis %d => %s, line %d\nMessage "
"terminated due to reason %i\n",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__, eomReason);
}
/* /*
Calculate the number of received responses by counting the number of Calculate the number of received responses by counting the number of
@ -358,23 +397,21 @@ asynStatus turboPmacController::writeRead(int axisNo, const char *command,
if (numExpectedResponses != numReceivedResponses) { if (numExpectedResponses != numReceivedResponses) {
adjustResponseForPrint(modResponse, response, MAXBUF_); adjustResponseForPrint(modResponse, response, MAXBUF_);
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nUnexpected response '%s' (carriage " "Controller \"%s\", axis %d => %s, line %d\nUnexpected "
"returns are replaced with spaces) for command %s\n", "response '%s' (carriage returns are replaced with spaces) "
__PRETTY_FUNCTION__, __LINE__, modResponse, command); "for command %s\n",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__, modResponse,
command);
snprintf(drvMessageText, sizeof(drvMessageText), snprintf(drvMessageText, sizeof(drvMessageText),
"Received unexpected response '%s' (carriage returns " "Received unexpected response '%s' (carriage returns "
"are replaced with spaces) for command %s. " "are replaced with spaces) for command %s. "
"Please call the support", "Please call the support",
modResponse, command); modResponse, command);
pl_status = setStringParam(motorMessageText_, drvMessageText);
if (pl_status != asynSuccess) {
return paramLibAccessFailed(pl_status, "motorMessageText_",
__PRETTY_FUNCTION__, __LINE__);
}
status = asynError; status = asynError;
} }
// Create custom error messages for different failure modes // Create custom error messages for different failure modes, if no error
// message has been set yet
if (strlen(drvMessageText) == 0) { if (strlen(drvMessageText) == 0) {
switch (status) { switch (status) {
case asynSuccess: case asynSuccess:
@ -402,43 +439,48 @@ asynStatus turboPmacController::writeRead(int axisNo, const char *command,
if (status == asynSuccess) { if (status == asynSuccess) {
asynPrint(lowLevelPortUser_, ASYN_TRACEIO_DRIVER, asynPrint(lowLevelPortUser_, ASYN_TRACEIO_DRIVER,
"%s => line %d:\nDevice response: %s (_ are " "Controller \"%s\", axis %d => %s, line %d\nDevice "
"carriage returns)\n", "response: %s (carriage returns are replaced with spaces)\n",
__PRETTY_FUNCTION__, __LINE__, modResponse); portName, axisNo, __PRETTY_FUNCTION__, __LINE__, modResponse);
pl_status = axis->setIntegerParam(this->motorStatusCommsError_, 0); paramLibStatus = axis->setIntegerParam(this->motorStatusCommsError_, 0);
} else { } else {
asynPrint(lowLevelPortUser_, ASYN_TRACE_ERROR, asynPrint(lowLevelPortUser_, ASYN_TRACE_ERROR,
"%s => line %d:\nCommunication failed for command %s (%s)\n", "Controller \"%s\", axis %d => %s, line %d\nCommunication "
__PRETTY_FUNCTION__, __LINE__, fullCommand, "failed for command %s (%s)\n",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__, fullCommand,
stringifyAsynStatus(status)); stringifyAsynStatus(status));
// Check if the axis already is in an error communication mode. If it is // Check if the axis already is in an error communication mode. If it is
// not, upstream the error. This is done to avoid "flooding" the user // not, upstream the error. This is done to avoid "flooding" the user
// with different error messages if more than one error ocurred before // with different error messages if more than one error ocurred before
// an error-free communication // an error-free communication
pl_status = paramLibStatus =
getIntegerParam(axisNo, motorStatusProblem_, &motorStatusProblem); getIntegerParam(axisNo, motorStatusProblem_, &motorStatusProblem);
if (pl_status != asynSuccess) { if (paramLibStatus != asynSuccess) {
return paramLibAccessFailed(pl_status, "motorStatusProblem_", return paramLibAccessFailed(paramLibStatus, "motorStatusProblem_",
__PRETTY_FUNCTION__, __LINE__); axisNo, __PRETTY_FUNCTION__, __LINE__);
} }
if (motorStatusProblem == 0) { if (motorStatusProblem == 0) {
pl_status = axis->setStringParam(motorMessageText_, drvMessageText); paramLibStatus =
if (pl_status != asynSuccess) { axis->setStringParam(motorMessageText_, drvMessageText);
return paramLibAccessFailed(pl_status, "motorMessageText_", if (paramLibStatus != asynSuccess) {
return paramLibAccessFailed(paramLibStatus, "motorMessageText_",
axisNo, __PRETTY_FUNCTION__,
__LINE__);
}
paramLibStatus = axis->setIntegerParam(motorStatusProblem_, 1);
if (paramLibStatus != asynSuccess) {
return paramLibAccessFailed(paramLibStatus,
"motorStatusProblem", axisNo,
__PRETTY_FUNCTION__, __LINE__); __PRETTY_FUNCTION__, __LINE__);
} }
pl_status = axis->setIntegerParam(motorStatusProblem_, 1); paramLibStatus = axis->setIntegerParam(motorStatusProblem_, 1);
if (pl_status != asynSuccess) { if (paramLibStatus != asynSuccess) {
return paramLibAccessFailed(pl_status, "motorStatusProblem", return paramLibAccessFailed(paramLibStatus,
__PRETTY_FUNCTION__, __LINE__); "motorStatusCommsError_", axisNo,
}
pl_status = axis->setIntegerParam(motorStatusProblem_, 1);
if (pl_status != asynSuccess) {
return paramLibAccessFailed(pl_status, "motorStatusCommsError_",
__PRETTY_FUNCTION__, __LINE__); __PRETTY_FUNCTION__, __LINE__);
} }
} }
@ -462,7 +504,7 @@ asynStatus turboPmacController::writeInt32(asynUser *pasynUser,
if (function == rereadEncoderPosition_) { if (function == rereadEncoderPosition_) {
return axis->rereadEncoder(); return axis->rereadEncoder();
} else if (function == readConfig_) { } else if (function == readConfig_) {
return axis->atFirstPoll(); return axis->init();
} else { } else {
return sinqController::writeInt32(pasynUser, value); return sinqController::writeInt32(pasynUser, value);
} }
@ -519,80 +561,6 @@ asynStatus turboPmacCreateController(const char *portName,
return asynSuccess; return asynSuccess;
} }
/*
C wrapper for the axis constructor. Please refer to the turboPmacAxis
constructor documentation. The controller is read from the portName.
*/
asynStatus turboPmacCreateAxis(const char *portName, int axis) {
turboPmacAxis *pAxis;
/*
findAsynPortDriver is a asyn library FFI function which uses the C ABI.
Therefore it returns a void pointer instead of e.g. a pointer to a
superclass of the controller such as asynPortDriver. Type-safe upcasting
via dynamic_cast is therefore not possible directly. However, we do know
that the void pointer is either a pointer to asynPortDriver (if a driver
with the specified name exists) or a nullptr. Therefore, we first do a
nullptr check, then a cast to asynPortDriver and lastly a (typesafe)
dynamic_upcast to Controller
https://stackoverflow.com/questions/70906749/is-there-a-safe-way-to-cast-void-to-class-pointer-in-c
*/
void *ptr = findAsynPortDriver(portName);
if (ptr == nullptr) {
/*
We can't use asynPrint here since this macro would require us
to get a lowLevelPortUser_ from a pointer to an asynPortDriver.
However, the given pointer is a nullptr and therefore doesn't
have a lowLevelPortUser_! printf is an EPICS alternative which
works w/o that, but doesn't offer the comfort provided
by the asynTrace-facility
*/
errlogPrintf("%s => line %d:\nPort %s not found.", __PRETTY_FUNCTION__,
__LINE__, portName);
return asynError;
}
// Unsafe cast of the pointer to an asynPortDriver
asynPortDriver *apd = (asynPortDriver *)(ptr);
// Safe downcast
turboPmacController *pC = dynamic_cast<turboPmacController *>(apd);
if (pC == nullptr) {
errlogPrintf("%s => line %d:\ncontroller on port %s is not a "
"turboPmacController.",
__PRETTY_FUNCTION__, __LINE__, portName);
return asynError;
}
// Prevent manipulation of the controller from other threads while we
// create the new axis.
pC->lock();
/*
We create a new instance of the axis, using the "new" keyword to
allocate it on the heap while avoiding RAII.
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.
*/
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-variable"
pAxis = new turboPmacAxis(pC, axis);
// Allow manipulation of the controller again
pC->unlock();
return asynSuccess;
}
/*
This is boilerplate code which is used to make the FFI functions
CreateController and CreateAxis "known" to the IOC shell (iocsh).
*/
#ifdef vxWorks
#else
/* /*
Define name and type of the arguments for the CreateController function Define name and type of the arguments for the CreateController function
in the iocsh. This is done by creating structs with the argument names and in the iocsh. This is done by creating structs with the argument names and
@ -616,36 +584,17 @@ static const iocshArg *const CreateControllerArgs[] = {
&CreateControllerArg3, &CreateControllerArg4, &CreateControllerArg5}; &CreateControllerArg3, &CreateControllerArg4, &CreateControllerArg5};
static const iocshFuncDef configturboPmacCreateController = { static const iocshFuncDef configturboPmacCreateController = {
"turboPmacController", 6, CreateControllerArgs}; "turboPmacController", 6, CreateControllerArgs};
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);
} }
/*
Same procedure as for the CreateController function, but for the axis
itself.
*/
static const iocshArg CreateAxisArg0 = {"Controller name (e.g. mcu1)",
iocshArgString};
static const iocshArg CreateAxisArg1 = {"Axis number", iocshArgInt};
static const iocshArg *const CreateAxisArgs[] = {&CreateAxisArg0,
&CreateAxisArg1};
static const iocshFuncDef configturboPmacCreateAxis = {"turboPmacAxis", 2,
CreateAxisArgs};
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 // 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 // in order to register both functions in the IOC shell
static void turboPmacRegister(void) { static void turboPmacControllerRegister(void) {
iocshRegister(&configturboPmacCreateController, iocshRegister(&configturboPmacCreateController,
configturboPmacCreateControllerCallFunc); configTurboPmacCreateControllerCallFunc);
iocshRegister(&configturboPmacCreateAxis,
configturboPmacCreateAxisCallFunc);
} }
epicsExportRegistrar(turboPmacRegister); epicsExportRegistrar(turboPmacControllerRegister);
#endif
} // extern "C" } // extern "C"

View File

@ -1,16 +1,16 @@
/******************************************** /********************************************
* turboPmacController.h * turboPmacController.h
* *
* PMAC V3 controller driver based on the asynMotorController class * Turbo PMAC controller driver based on the asynMotorController class
* *
* Stefan Mathis, September 2024 * Stefan Mathis, September 2024
********************************************/ ********************************************/
#ifndef turboPmacController_H #ifndef turboPmacController_H
#define turboPmacController_H #define turboPmacController_H
#include "turboPmacAxis.h"
#include "sinqAxis.h" #include "sinqAxis.h"
#include "sinqController.h" #include "sinqController.h"
#include "turboPmacAxis.h"
class turboPmacController : public sinqController { class turboPmacController : public sinqController {
@ -28,14 +28,15 @@ class turboPmacController : public sinqController {
time (in seconds) has passed, then it declares a timeout. time (in seconds) has passed, then it declares a timeout.
*/ */
turboPmacController(const char *portName, const char *ipPortConfigName, turboPmacController(const char *portName, const char *ipPortConfigName,
int numAxes, double movingPollPeriod, int numAxes, double movingPollPeriod,
double idlePollPeriod, double comTimeout); double idlePollPeriod, double comTimeout);
/** /**
* @brief Get the axis object * @brief Get the axis object
* *
* @param pasynUser Specify the axis via the asynUser * @param pasynUser Specify the axis via the asynUser
* @return turboPmacAxis* If no axis could be found, this is a nullptr * @return turboPmacAxis* If no axis could be found, this is a
* nullptr
*/ */
turboPmacAxis *getAxis(asynUser *pasynUser); turboPmacAxis *getAxis(asynUser *pasynUser);
@ -43,7 +44,8 @@ class turboPmacController : public sinqController {
* @brief Get the axis object * @brief Get the axis object
* *
* @param axisNo Specify the axis via its index * @param axisNo Specify the axis via its index
* @return turboPmacAxis* If no axis could be found, this is a nullptr * @return turboPmacAxis* If no axis could be found, this is a
* nullptr
*/ */
turboPmacAxis *getAxis(int axisNo); turboPmacAxis *getAxis(int axisNo);
@ -81,8 +83,8 @@ class turboPmacController : public sinqController {
int numExpectedResponses); int numExpectedResponses);
/** /**
* @brief Save cast of the given asynAxis pointer to a turboPmacAxis pointer. * @brief Save cast of the given asynAxis pointer to a turboPmacAxis
* If the cast fails, this function returns a nullptr. * pointer. If the cast fails, this function returns a nullptr.
* *
* @param asynAxis * @param asynAxis
* @return turboPmacAxis* * @return turboPmacAxis*
@ -119,7 +121,7 @@ class turboPmacController : public sinqController {
static const uint32_t MAXBUF_ = 200; static const uint32_t MAXBUF_ = 200;
/* /*
Stores the constructor input comTimeout Timeout for the communication process in seconds
*/ */
double comTimeout_; double comTimeout_;
@ -133,6 +135,7 @@ class turboPmacController : public sinqController {
friend class turboPmacAxis; friend class turboPmacAxis;
}; };
#define NUM_turboPmac_DRIVER_PARAMS (&LAST_turboPmac_PARAM - &FIRST_turboPmac_PARAM + 1) #define NUM_turboPmac_DRIVER_PARAMS \
(&LAST_turboPmac_PARAM - &FIRST_turboPmac_PARAM + 1)
#endif /* turboPmacController_H */ #endif /* turboPmacController_H */