Rebased pmacV3 onto sinqMotor
This commit is contained in:
4
src/pmacV3.dbd
Normal file
4
src/pmacV3.dbd
Normal file
@@ -0,0 +1,4 @@
|
||||
#---------------------------------------------
|
||||
# SINQ specific DB definitions
|
||||
#---------------------------------------------
|
||||
registrar(pmacV3Register)
|
||||
1163
src/pmacV3Axis.cpp
Normal file
1163
src/pmacV3Axis.cpp
Normal file
File diff suppressed because it is too large
Load Diff
131
src/pmacV3Axis.h
Normal file
131
src/pmacV3Axis.h
Normal 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
678
src/pmacV3Controller.cpp
Normal 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
140
src/pmacV3Controller.h
Normal 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 */
|
||||
Reference in New Issue
Block a user