Rebased pmacV3 onto sinqMotor

This commit is contained in:
2024-11-20 12:02:33 +01:00
parent 910c5fa072
commit aaca07c2b6
9 changed files with 2451 additions and 84 deletions

4
src/pmacV3.dbd Normal file
View File

@@ -0,0 +1,4 @@
#---------------------------------------------
# SINQ specific DB definitions
#---------------------------------------------
registrar(pmacV3Register)

1163
src/pmacV3Axis.cpp Normal file

File diff suppressed because it is too large Load Diff

131
src/pmacV3Axis.h Normal file
View File

@@ -0,0 +1,131 @@
#ifndef pmacV3AXIS_H
#define pmacV3AXIS_H
#include "sinqAxis.h"
// Forward declaration of the controller class to resolve the cyclic dependency
// between C804Controller.h and C804Axis.h. See
// https://en.cppreference.com/w/cpp/language/class.
class pmacV3Controller;
class pmacV3Axis : public sinqAxis {
public:
/**
* @brief Construct a new pmacV3Axis
*
* @param pController Pointer to the associated controller
* @param axisNo Index of the axis
* @param offsetLimit The high and low limits of the axis are read
* out directly from the MCU. However, since the axis might slightly
"overshoot" when moving to a position next to the limits, the MCU might go
into the "limits hit" error state. To prevent this, this value allows adding
a small offset, which is subtracted from the high limit and added to the
low limit.
*/
pmacV3Axis(pmacV3Controller *pController, int axisNo, double offsetLimit);
/**
* @brief Destroy the pmacV3Axis
*
*/
virtual ~pmacV3Axis();
/**
* @brief Implementation of the `stop` function from asynMotorAxis
*
* @param acceleration Acceleration ACCEL from the motor record. This
* value is currently not used.
* @return asynStatus
*/
asynStatus stop(double acceleration);
/**
* @brief Implementation of the `doHome` function from sinqAxis. The
* parameters are described in the documentation of `sinqAxis::doHome`.
*
* @param minVelocity
* @param maxVelocity
* @param acceleration
* @param forwards
* @return asynStatus
*/
asynStatus doHome(double minVelocity, double maxVelocity,
double acceleration, int forwards);
/**
* @brief Implementation of the `doPoll` function from sinqAxis. The
* parameters are described in the documentation of `sinqAxis::doPoll`.
*
* @param moving
* @return asynStatus
*/
asynStatus doPoll(bool *moving);
/**
* @brief Implementation of the `doMove` function from sinqAxis. The
* parameters are described in the documentation of `sinqAxis::doMove`.
*
* @param position
* @param relative
* @param min_velocity
* @param max_velocity
* @param acceleration
* @return asynStatus
*/
asynStatus doMove(double position, int relative, double min_velocity,
double max_velocity, double acceleration);
/**
* @brief Implementation of the `atFirstPoll` function from sinqAxis.
*
* 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 accordint to the initial status of the axis.
*
* @return asynStatus
*/
asynStatus atFirstPoll();
/**
* @brief Enable / disable the axis.
*
* @param on
* @return asynStatus
*/
asynStatus enable(bool on);
/**
* @brief Read the encoder type (incremental or absolute) for this axis from
* the MCU and store the information in the PV ENCODER_TYPE.
*
* @return asynStatus
*/
asynStatus readEncoderType();
/**
* @brief Trigger a rereading of the encoder position.
*
* @return asynStatus
*/
asynStatus rereadEncoder();
protected:
pmacV3Controller *pC_;
asynStatus readConfig();
bool initial_poll_;
bool waitForHandshake_;
time_t timeAtHandshake_;
// The axis status is used when enabling / disabling the motor
int axisStatus_;
/*
Stores the constructor input offsetLimits
*/
double offsetLimits_;
private:
friend class pmacV3Controller;
};
#endif

678
src/pmacV3Controller.cpp Normal file
View File

@@ -0,0 +1,678 @@
#include "pmacV3Controller.h"
#include "asynMotorController.h"
#include "asynOctetSyncIO.h"
#include "pmacV3Axis.h"
#include <epicsExport.h>
#include <errlog.h>
#include <iocsh.h>
#include <netinet/in.h>
#include <registryFunction.h>
#include <string.h>
#include <unistd.h>
/**
* @brief Construct a new pmacV3Controller::pmacV3Controller object
*
* @param portName See documentation of sinqController
* @param ipPortConfigName See documentation of sinqController
* @param numAxes See documentation of sinqController
* @param movingPollPeriod See documentation of sinqController
* @param idlePollPeriod See documentation of sinqController
* @param comTimeout Time after which a communication timeout error
* is declared in writeRead (in seconds)
* @param extraParams See documentation of sinqController
*/
pmacV3Controller::pmacV3Controller(const char *portName,
const char *ipPortConfigName, int numAxes,
double movingPollPeriod,
double idlePollPeriod, double comTimeout)
: sinqController(
portName, ipPortConfigName, numAxes, movingPollPeriod, idlePollPeriod,
/*
The following parameter library entries are added in this driver:
- ENABLE_AXIS
- AXIS_ENABLED
- ENCODER_TYPE
- REREAD_ENCODER_POSITION
- REREAD_ENCODER_POSITION_RBV
- READ_CONFIG
- MOTOR_HIGH_LIMIT_FROM_DRIVER
- MOTOR_LOW_LIMIT_FROM_DRIVER
*/
8)
{
// Initialization of local variables
asynStatus status = asynSuccess;
// Initialization of all member variables
lowLevelPortUser_ = nullptr;
comTimeout_ = comTimeout;
// =========================================================================;
/*
We try to connect to the port via the port name provided by the constructor.
If this fails, the function is terminated via exit
*/
pasynOctetSyncIO->connect(ipPortConfigName, 0, &lowLevelPortUser_, NULL);
if (status != asynSuccess || lowLevelPortUser_ == nullptr) {
errlogPrintf(
"%s => line %d:\nFATAL ERROR (cannot connect to MCU controller).\n"
"Terminating IOC",
__PRETTY_FUNCTION__, __LINE__);
exit(-1);
}
// =========================================================================
// Create additional parameter library entries
status = createParam("ENABLE_AXIS", asynParamInt32, &enableMotor_);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (creating a parameter failed "
"with %s).\nTerminating IOC",
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status));
exit(-1);
}
status = createParam("AXIS_ENABLED", asynParamInt32, &motorEnabled_);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (creating a parameter failed "
"with %s).\nTerminating IOC",
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status));
exit(-1);
}
status = createParam("ENCODER_TYPE", asynParamOctet, &encoderType_);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (creating a parameter failed "
"with %s).\nTerminating IOC",
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status));
exit(-1);
}
status = createParam("REREAD_ENCODER_POSITION", asynParamInt32,
&rereadEncoderPosition_);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (creating a parameter failed "
"with %s).\nTerminating IOC",
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status));
exit(-1);
}
status = createParam("REREAD_ENCODER_POSITION_RBV", asynParamInt32,
&rereadEncoderPositionRBV_);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (creating a parameter failed "
"with %s).\nTerminating IOC",
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status));
exit(-1);
}
status = createParam("READ_CONFIG", asynParamInt32, &readConfig_);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (creating a parameter failed "
"with %s).\nTerminating IOC",
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status));
exit(-1);
}
/*
We need to introduce 2 new parameters in order to write the limits from the
driver to the EPICS record. See the comment in pmacV3Controller.h next to
the declaration of motorHighLimitFromDriver_.
*/
status = createParam("MOTOR_HIGH_LIMIT_FROM_DRIVER", asynParamFloat64,
&motorHighLimitFromDriver_);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (creating a parameter failed "
"with %s).\nTerminating IOC",
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status));
exit(-1);
}
status = createParam("MOTOR_LOW_LIMIT_FROM_DRIVER", asynParamFloat64,
&motorLowLimitFromDriver_);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (creating a parameter failed "
"with %s).\nTerminating IOC",
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status));
exit(-1);
}
/*
Define the end-of-string of a message coming from the device to EPICS.
It is not necessary to append a terminator to outgoing messages, since
the message length is encoded in the message header in the getSetResponse
method.
*/
const char *message_from_device =
"\006"; // Hex-code for ACK (acknowledge) -> Each message from the MCU
// is terminated by this value
status = pasynOctetSyncIO->setInputEos(
lowLevelPortUser_, message_from_device, strlen(message_from_device));
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (setting input EOS failed "
"with %s).\nTerminating IOC",
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status));
pasynOctetSyncIO->disconnect(lowLevelPortUser_);
exit(-1);
}
status = callParamCallbacks();
if (status != asynSuccess) {
asynPrint(
this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (executing ParamLib callbacks failed "
"with %s).\nTerminating IOC",
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status));
pasynOctetSyncIO->disconnect(lowLevelPortUser_);
exit(-1);
}
}
/*
Access one of the axes of the controller via the axis adress stored in asynUser.
If the axis does not exist or is not a Axis, a nullptr is returned and an
error is emitted.
*/
pmacV3Axis *pmacV3Controller::getAxis(asynUser *pasynUser) {
asynMotorAxis *asynAxis = asynMotorController::getAxis(pasynUser);
return pmacV3Controller::castToAxis(asynAxis);
}
/*
Access one of the axes of the controller via the axis index.
If the axis does not exist or is not a Axis, the function must return Null
*/
pmacV3Axis *pmacV3Controller::getAxis(int axisNo) {
asynMotorAxis *asynAxis = asynMotorController::getAxis(axisNo);
return pmacV3Controller::castToAxis(asynAxis);
}
pmacV3Axis *pmacV3Controller::castToAxis(asynMotorAxis *asynAxis) {
// =========================================================================
// If the axis slot of the pAxes_ array is empty, a nullptr must be returned
if (asynAxis == nullptr) {
return nullptr;
}
// Here, an error is emitted since asyn_axis is not a nullptr but also not
// an instance of Axis
pmacV3Axis *axis = dynamic_cast<pmacV3Axis *>(asynAxis);
if (axis == nullptr) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nAxis %d is not an instance of pmacV3Axis",
__PRETTY_FUNCTION__, __LINE__, axis->axisNo_);
}
return axis;
}
asynStatus pmacV3Controller::writeRead(int axisNo, const char *command,
char *response,
int numExpectedResponses) {
// Definition of local variables.
asynStatus status = asynSuccess;
asynStatus pl_status = asynSuccess;
char full_command[MAXBUF_] = {0};
char user_message[MAXBUF_] = {0};
int motorStatusProblem = 0;
int numReceivedResponses = 0;
// Send the message and block the thread until either a response has
// been received or the timeout is triggered
int eomReason = 0; // Flag indicating why the message has ended
// Number of bytes of the outgoing message (which is command + the
// end-of-string terminator defined in the constructor)
size_t nbytesOut = 0;
// Number of bytes of the incoming message (which is response + the
// end-of-string terminator defined in the constructor)
size_t nbytesIn = 0;
// =========================================================================
pmacV3Axis *axis = getAxis(axisNo);
if (axis == nullptr) {
// We already did the error logging directly in getAxis
return asynError;
}
/*
The message protocol of the pmacV3 used at PSI looks as follows (all
characters immediately following each other without a newline):
0x40 (ASCII value of @) -> Request for download
0xBF (ASCII value of ¿) -> Select mode "get_response"
0x00 (ASCII value of 0)
0x00 (ASCII value of 0)
0x00 (ASCII value of 0)
0x00 (ASCII value of 0)
0x00 (ASCII value of 0)
[message length in network byte order] -> Use the htons function for this
value [Actual message] It is not necessary to append a terminator, since
this protocol encodes the message length at the beginning. See Turbo PMAC
User Manual, page 418 in VR_PMAC_GETRESPONSE
The message has to be build manually into the buffer full_command, since it
contains NULL terminators in its middle, therefore the string manipulation
methods of C don't work.
*/
// The entire message is equal to the command length
const size_t commandLength =
strlen(command) + 1; // +1 because of the appended /r
const int offset = 8;
// Positions 2 to 6 must have the value 0. Since full_command is initialized
// as an array of zeros, we don't need to set these bits manually.
full_command[0] = '\x40';
full_command[1] = '\xBF';
full_command[7] = commandLength;
snprintf((char *)full_command + offset, MAXBUF_ - offset, "%s\r", command);
asynPrint(this->pasynUserSelf, ASYN_TRACEIO_DRIVER,
"%s => line %d:\nSending command %s", __PRETTY_FUNCTION__,
__LINE__, full_command);
// Perform the actual writeRead
status = pasynOctetSyncIO->writeRead(
lowLevelPortUser_, full_command, commandLength + offset, response,
MAXBUF_, comTimeout_, &nbytesOut, &nbytesIn, &eomReason);
/*
Calculate the number of received responses by counting the number of
carriage returns "\r" in the response.
*/
for (size_t i = 0; i < strlen(response); i++) {
if (response[i] == '\r') {
numReceivedResponses++;
}
}
/*
Check if we got the expected amount of responses. If we didn't, flush the
PMAC and try again. If that fails as well, return an error.
*/
if (numExpectedResponses != numReceivedResponses) {
// Flush message as defined in Turbo PMAC User Manual, p. 430:
// \x40\xB3000
// VR_DOWNLOAD = \x40
// VR_PMAC_FLUSH = \xB3
char flush_msg[5] = {0};
flush_msg[0] = '\x40';
flush_msg[1] = '\xB3';
size_t nbytesOut = 0;
status = pasynOctetSyncIO->write(lowLevelPortUser_, flush_msg, 5,
comTimeout_, &nbytesOut);
// Wait after the flush so the MCU has time to prepare for the
// next command
usleep(100000);
if (status == asynSuccess) {
// If flushing the MCU succeded, try to send the command again
status = pasynOctetSyncIO->writeRead(
lowLevelPortUser_, full_command, commandLength + offset,
response, MAXBUF_, comTimeout_, &nbytesOut, &nbytesIn,
&eomReason);
// If the command returned a bad answer for the second time, give up
// and propagate the problem
numReceivedResponses = 0;
for (size_t i = 0; i < strlen(response); i++) {
if (response[i] == '\r') {
numReceivedResponses++;
}
}
// Second check: If this fails, give up and propagate the error.
if (numExpectedResponses != numReceivedResponses) {
asynPrint(
this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nUnexpected response %s for command %s\n",
__PRETTY_FUNCTION__, __LINE__, response, command);
snprintf(user_message, sizeof(user_message),
"Received unexpected response %s for command %s. "
"Please call the support",
response, command);
pl_status = setStringParam(motorMessageText_, user_message);
if (pl_status != asynSuccess) {
return paramLibAccessFailed(pl_status, "motorMessageText_",
__PRETTY_FUNCTION__, __LINE__);
}
status = asynError;
}
} else {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFlushing the MCU failed with %s\n",
__PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(status));
}
}
// Create custom error messages for different failure modes
if (strlen(user_message) == 0) {
switch (status) {
case asynSuccess:
break; // Communicate nothing
case asynTimeout:
snprintf(user_message, sizeof(user_message),
"connection timeout for axis %d", axisNo);
break;
case asynDisconnected:
snprintf(user_message, sizeof(user_message),
"axis is not connected");
break;
case asynDisabled:
snprintf(user_message, sizeof(user_message), "axis is disabled");
break;
default:
snprintf(user_message, sizeof(user_message),
"Communication failed (%s)", stringifyAsynStatus(status));
break;
}
}
if (status != asynSuccess) {
// 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
// with different error messages if more than one error ocurred before
// an error-free communication
pl_status =
getIntegerParam(axisNo, motorStatusProblem_, &motorStatusProblem);
if (pl_status != asynSuccess) {
return paramLibAccessFailed(pl_status, "motorStatusProblem_",
__PRETTY_FUNCTION__, __LINE__);
}
if (motorStatusProblem == 0) {
pl_status =
axis->setStringParam(this->motorMessageText_, user_message);
if (pl_status != asynSuccess) {
return paramLibAccessFailed(pl_status, "motorMessageText_",
__PRETTY_FUNCTION__, __LINE__);
}
}
}
// Log the overall status (communication successfull or not)
if (status == asynSuccess) {
asynPrint(lowLevelPortUser_, ASYN_TRACEIO_DRIVER,
"%s => line %d:\nDevice response: %s\n", __PRETTY_FUNCTION__,
__LINE__, response);
pl_status = axis->setIntegerParam(this->motorStatusCommsError_, 0);
} else {
if (status == asynSuccess) {
asynPrint(
lowLevelPortUser_, ASYN_TRACE_ERROR,
"%s => line %d:\nCommunication failed for command %s (%s)\n",
__PRETTY_FUNCTION__, __LINE__, full_command,
stringifyAsynStatus(status));
pl_status = axis->setIntegerParam(this->motorStatusCommsError_, 1);
}
if (pl_status != asynSuccess) {
return paramLibAccessFailed(pl_status, "motorStatusCommsError_",
__PRETTY_FUNCTION__, __LINE__);
}
}
return asynSuccess;
}
asynStatus pmacV3Controller::writeInt32(asynUser *pasynUser, epicsInt32 value) {
asynPrint(pasynUserSelf, ASYN_TRACE_ERROR, "enabling %d", value);
pmacV3Axis *axis = getAxis(pasynUser);
if (axis == nullptr) {
// We already did the error logging directly in getAxis
return asynError;
}
// Handle custom PVs
if (pasynUser->reason == enableMotor_) {
return axis->enable(value != 0);
} else if (pasynUser->reason == rereadEncoderPosition_) {
return axis->rereadEncoder();
} else {
return asynMotorController::writeInt32(pasynUser, value);
}
}
/*
Overloaded from asynMotorController because the special cases "motor
enabling" and "rereading the encoder" must be covered.
*/
asynStatus pmacV3Controller::readInt32(asynUser *pasynUser, epicsInt32 *value) {
int function = pasynUser->reason;
asynStatus status = asynError;
// =====================================================================
pmacV3Axis *axis = getAxis(pasynUser);
if (axis == nullptr) {
// We already did the error logging directly in getAxis
return asynError;
}
if (function == rereadEncoderPositionRBV_) {
// Readback value for rereadEncoderPosition
status = getIntegerParam(axis->axisNo_, rereadEncoderPosition_, value);
if (status != asynSuccess) {
return paramLibAccessFailed(status, "rereadEncoderPosition_",
__PRETTY_FUNCTION__, __LINE__);
}
status =
setIntegerParam(axis->axisNo_, rereadEncoderPositionRBV_, *value);
if (status != asynSuccess) {
return paramLibAccessFailed(status, "rereadEncoderPositionRBV_",
__PRETTY_FUNCTION__, __LINE__);
}
// Update the PVs from the parameter library
return callParamCallbacks();
} else if (function == motorEnabled_) {
char command[MAXBUF_], response[MAXBUF_];
int axStatus = 0;
// Query the motor status from the affected axis
snprintf(command, sizeof(command), "P%2.2d00", axis->axisNo_);
status = writeRead(axis->axisNo_, command, response, 1);
if (status != asynSuccess) {
return status;
}
int nvals = sscanf(response, "%d", &axStatus);
if (nvals != 1) {
return errMsgCouldNotParseResponse(command, response, axis->axisNo_,
__PRETTY_FUNCTION__, __LINE__);
}
// Update the parameter library
return setIntegerParam(motorEnabled_,
(axStatus != -3 && axStatus != -5));
} else {
return asynMotorController::readInt32(pasynUser, value);
}
}
/*************************************************************************************/
/** The following functions are C-wrappers, and can be called directly from
* iocsh */
extern "C" {
/*
C wrapper for the controller constructor. Please refer to the pmacV3Controller
constructor documentation.
*/
asynStatus pmacV3CreateController(const char *portName,
const char *lowLevelPortName, int numAxes,
double movingPollPeriod,
double idlePollPeriod, double comTimeout) {
/*
We create a new instance of the controller, 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"
pmacV3Controller *pController =
new pmacV3Controller(portName, lowLevelPortName, numAxes,
movingPollPeriod, idlePollPeriod, comTimeout);
return asynSuccess;
}
/*
C wrapper for the axis constructor. Please refer to the pmacV3Axis constructor
documentation. The controller is read from the portName.
*/
asynStatus pmacV3CreateAxis(const char *portName, int axis,
double offsetLimits) {
pmacV3Axis *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
pmacV3Controller *pC = dynamic_cast<pmacV3Controller *>(apd);
if (pC == nullptr) {
errlogPrintf(
"%s => line %d:\ncontroller on port %s is not a pmacV3Controller.",
__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 pmacV3Axis(pC, axis, offsetLimits);
// 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
in the iocsh. This is done by creating structs with the argument names and
types and then providing "factory" functions
(configCreateControllerCallFunc). These factory functions are used to
register the constructors during compilation.
*/
static const iocshArg CreateControllerArg0 = {"Controller port name",
iocshArgString};
static const iocshArg CreateControllerArg1 = {"Low level port name",
iocshArgString};
static const iocshArg CreateControllerArg2 = {"Number of axes", iocshArgInt};
static const iocshArg CreateControllerArg3 = {"Moving poll rate (s)",
iocshArgDouble};
static const iocshArg CreateControllerArg4 = {"Idle poll rate (s)",
iocshArgDouble};
static const iocshArg CreateControllerArg5 = {"Communication timeout (s)",
iocshArgDouble};
static const iocshArg *const CreateControllerArgs[] = {
&CreateControllerArg0, &CreateControllerArg1, &CreateControllerArg2,
&CreateControllerArg3, &CreateControllerArg4, &CreateControllerArg5};
static const iocshFuncDef configPmacV3CreateController = {
"revisedPmacV3CreateController", 6, CreateControllerArgs};
static void configPmacV3CreateControllerCallFunc(const iocshArgBuf *args) {
pmacV3CreateController(args[0].sval, args[1].sval, args[2].ival,
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 port name", iocshArgString};
static const iocshArg CreateAxisArg1 = {"Axis number", iocshArgInt};
static const iocshArg CreateAxisArg2 = {
"Offset for the MCU limits in mm or degree", iocshArgDouble};
static const iocshArg *const CreateAxisArgs[] = {
&CreateAxisArg0, &CreateAxisArg1, &CreateAxisArg2};
static const iocshFuncDef configPmacV3CreateAxis = {"revisedPmacV3CreateAxis",
3, CreateAxisArgs};
static void configPmacV3CreateAxisCallFunc(const iocshArgBuf *args) {
pmacV3CreateAxis(args[0].sval, args[1].ival, args[2].dval);
}
// This function is made known to EPICS in pmacV3.dbd and is called by EPICS
// in order to register both functions in the IOC shell
static void pmacV3Register(void) {
iocshRegister(&configPmacV3CreateController,
configPmacV3CreateControllerCallFunc);
iocshRegister(&configPmacV3CreateAxis, configPmacV3CreateAxisCallFunc);
}
epicsExportRegistrar(pmacV3Register);
#endif
} // extern "C"

140
src/pmacV3Controller.h Normal file
View File

@@ -0,0 +1,140 @@
/********************************************
* pmacV3Controller.h
*
* PMAC V3 controller driver based on the asynMotorController class
*
* Stefan Mathis, September 2024
********************************************/
#ifndef pmacV3Controller_H
#define pmacV3Controller_H
#include "pmacV3Axis.h"
#include "sinqAxis.h"
#include "sinqController.h"
#define IncrementalEncoder "Incremental encoder"
#define AbsoluteEncoder "Absolute encoder"
class pmacV3Controller : public sinqController {
public:
/**
* @brief Construct a new pmacV3Controller object
*
* @param portName See sinqController constructor
* @param ipPortConfigName See sinqController constructor
* @param numAxes See sinqController constructor
* @param movingPollPeriod See sinqController constructor
* @param idlePollPeriod See sinqController constructor
* @param comTimeout When trying to communicate with the device,
the underlying asynOctetSyncIO interface waits for a response until this
time (in seconds) has passed, then it declares a timeout.
*/
pmacV3Controller(const char *portName, const char *ipPortConfigName,
int numAxes, double movingPollPeriod,
double idlePollPeriod, double comTimeout);
/**
* @brief Get the axis object
*
* @param pasynUser Specify the axis via the asynUser
* @return pmacV3Axis* If no axis could be found, this is a nullptr
*/
pmacV3Axis *getAxis(asynUser *pasynUser);
/**
* @brief Get the axis object
*
* @param axisNo Specify the axis via its index
* @return pmacV3Axis* If no axis could be found, this is a nullptr
*/
pmacV3Axis *getAxis(int axisNo);
/**
* @brief Overloaded function of asynMotorController
*
* The function is overloaded to allow enabling / disabling the motor and
* rereading the encoder.
*
* @param pasynUser Specify the axis via the asynUser
* @param value New value
* @return asynStatus
*/
asynStatus writeInt32(asynUser *pasynUser, epicsInt32 value);
/**
* @brief Overloaded function of asynMotorController
*
* The function is overloaded to get readback values for the enabling /
* disabling status and the encoder.
*
* @param pasynUser Specify the axis via the asynUser
* @param value Read-out value
* @return asynStatus
*/
asynStatus readInt32(asynUser *pasynUser, epicsInt32 *value);
protected:
asynUser *lowLevelPortUser_;
/**
* @brief Send a command to the hardware and receive a response
*
* @param axisNo Axis to which the command should be send
* @param command Command for the hardware
* @param response Buffer for the response. This buffer is
* expected to have the size MAXBUF_.
* @param numExpectedResponses The PMAC MCU can send multiple responses at
* once. The number of responses is determined by the number of
* "subcommands" within command. Therefore it is known in advance how many
* "subresponses" are expected. This can be used to check the integrity of
* the received response, since the subresponses are separated by carriage
* returns (/r). The number of carriage returns is compared to
* numExpectedResponses to determine if the communication was successfull.
* @return asynStatus
*/
asynStatus writeRead(int axisNo, const char *command, char *response,
int numExpectedResponses);
/**
* @brief Save cast of the given asynAxis pointer to a pmacV3Axis pointer.
* If the cast fails, this function returns a nullptr.
*
* @param asynAxis
* @return pmacV3Axis*
*/
pmacV3Axis *castToAxis(asynMotorAxis *asynAxis);
private:
// Set the maximum buffer size. This is an empirical value which must be
// large enough to avoid overflows for all commands to the device /
// responses from it.
static const uint32_t MAXBUF_ = 200;
/*
Stores the constructor input comTimeout
*/
double comTimeout_;
// Indices of additional PVs
int enableMotor_;
int motorEnabled_;
int rereadEncoderPosition_;
int rereadEncoderPositionRBV_;
int readConfig_;
int encoderType_;
/*
These parameters are here to write the high and low limits from the MCU to
the EPICS motor record. Using motorHighLimit_ / motorLowLimit_ does not
work: https://epics.anl.gov/tech-talk/2023/msg00576.php.
Therefore, some additional records are introduced which read from these
parameters and write into the motor record. See the sinq_asyn_motor.db file.
*/
int motorHighLimitFromDriver_;
int motorLowLimitFromDriver_;
friend class pmacV3Axis;
};
#endif /* pmacV3Controller_H */