Files
mastermacs/src/masterMacsAxis.cpp
2025-03-04 14:19:19 +01:00

1235 lines
46 KiB
C++

#include "masterMacsAxis.h"
#include "asynOctetSyncIO.h"
#include "epicsExport.h"
#include "iocsh.h"
#include "masterMacsController.h"
#include <bitset>
#include <cmath>
#include <errlog.h>
#include <limits>
#include <math.h>
#include <string.h>
#include <unistd.h>
/*
Contains all instances of turboPmacAxis which have been created and is used in
the initialization hook function.
*/
static std::vector<masterMacsAxis *> axes;
/**
* @brief Hook function to perform certain actions during the IOC initialization
*
* @param iState
*/
static void epicsInithookFunction(initHookState iState) {
if (iState == initHookAfterDatabaseRunning) {
// Iterate through all axes of each and call the initialization method
// on each one of them.
for (std::vector<masterMacsAxis *>::iterator itA = axes.begin();
itA != axes.end(); ++itA) {
masterMacsAxis *axis = *itA;
axis->init();
}
}
}
void appendErrorMessage(char *fullMessage, size_t capacityFullMessage,
const char *toBeAppended) {
size_t lenFullMessage = strlen(fullMessage);
size_t lenToBeAppended = strlen(toBeAppended);
if (lenFullMessage == 0) {
// The error message is empty -> Just copy the content of toBeAppended
// into fullMessage, if the formers capacity suffices
if (lenToBeAppended < capacityFullMessage) {
// cppcheck-suppress unsafeStrcpy
strcpy(fullMessage, toBeAppended);
}
} else {
// Append the message and add a linebreak in between, if the capacity of
// fullMessage suffices. We need capacity for one additional character
// because of the linebreak.
if (lenFullMessage + lenToBeAppended + 1 < capacityFullMessage) {
// Append the linebreak and readd the null terminator behind it
// fullMessage[lenFullMessage] = '\n';
// fullMessage[lenFullMessage + 1] = '\0';
// Append the actual message
strcat(fullMessage, toBeAppended);
}
}
}
masterMacsAxis::masterMacsAxis(masterMacsController *pC, int axisNo)
: sinqAxis(pC, axisNo), pC_(pC) {
asynStatus status = asynSuccess;
/*
The superclass constructor sinqAxis calls in turn its superclass constructor
asynMotorAxis. In the latter, a pointer to the constructed object this is
stored inside the array pAxes_:
pC->pAxes_[axisNo] = this;
Therefore, the axes are managed by the controller pC. If axisNo is out of
bounds, asynMotorAxis prints an error. However, we want the IOC creation to
stop completely, since this is a configuration error.
*/
if (axisNo >= pC->numAxes_) {
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d:: FATAL ERROR: "
"Axis index %d must be smaller than the total number of axes "
"%d. Call the support.",
pC_->portName, axisNo, __PRETTY_FUNCTION__, __LINE__, axisNo_,
pC->numAxes_);
exit(-1);
}
// Register the hook function during construction of the first axis object
if (axes.empty()) {
initHookRegister(&epicsInithookFunction);
}
// Collect all axes into this list which will be used in the hook function
axes.push_back(this);
// Initialize all member variables
axisStatus_ = std::bitset<16>(0);
axisError_ = std::bitset<16>(0);
// Initial value for the motor speed, is overwritten in atFirstPoll.
lastSetSpeed_ = 0.0;
// Flag for handshake waiting
waitForHandshake_ = false;
timeAtHandshake_ = 0;
// masterMacs motors can always be disabled
status = pC_->setIntegerParam(axisNo_, pC_->motorCanDisable_, 1);
if (status != asynSuccess) {
asynPrint(
pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d:\nFATAL ERROR "
"(setting a parameter value failed with %s)\n. Terminating IOC",
pC_->portName, axisNo, __PRETTY_FUNCTION__, __LINE__,
pC_->stringifyAsynStatus(status));
exit(-1);
}
// Assume that the motor is initially not moving
status = pC_->setIntegerParam(axisNo_, pC_->motorStatusMoving_, false);
if (status != asynSuccess) {
asynPrint(
pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d:\nFATAL ERROR "
"(setting a parameter value failed with %s)\n. Terminating IOC",
pC_->portName, axisNo, __PRETTY_FUNCTION__, __LINE__,
pC_->stringifyAsynStatus(status));
}
}
masterMacsAxis::~masterMacsAxis(void) {
// Since the controller memory is managed somewhere else, we don't need to
// clean up the pointer pC here.
}
/**
Read out the following values:
- current position
- current velocity
- maximum velocity
- acceleration
- Software limits
*/
asynStatus masterMacsAxis::init() {
// Local variable declaration
asynStatus pl_status = asynSuccess;
char response[pC_->MAXBUF_];
int nvals = 0;
double motorRecResolution = 0.0;
double motorPosition = 0.0;
double motorVelocity = 0.0;
double motorVmax = 0.0;
double motorAccel = 0.0;
// =========================================================================
// The parameter library takes some time to be initialized. Therefore we
// wait until the status is not asynParamUndefined anymore.
time_t now = time(NULL);
time_t maxInitTime = 60;
while (1) {
pl_status = pC_->getDoubleParam(axisNo_, pC_->motorRecResolution_,
&motorRecResolution);
if (pl_status == asynParamUndefined) {
if (now + maxInitTime < time(NULL)) {
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line "
"%d\nInitializing the parameter library failed.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__,
__LINE__);
return asynError;
}
} else if (pl_status == asynSuccess) {
break;
} else if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorRecResolution_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
}
// Read out the current position
pl_status = pC_->read(axisNo_, 12, response);
if (pl_status != asynSuccess) {
return pl_status;
}
nvals = sscanf(response, "%lf", &motorPosition);
if (nvals != 1) {
return pC_->errMsgCouldNotParseResponse("R12", response, axisNo_,
__PRETTY_FUNCTION__, __LINE__);
}
// Read out the current velocity
pl_status = pC_->read(axisNo_, 05, response);
if (pl_status != asynSuccess) {
return pl_status;
}
nvals = sscanf(response, "%lf", &motorVelocity);
if (nvals != 1) {
return pC_->errMsgCouldNotParseResponse("R05", response, axisNo_,
__PRETTY_FUNCTION__, __LINE__);
}
// Read out the maximum velocity
pl_status = pC_->read(axisNo_, 26, response);
if (pl_status != asynSuccess) {
return pl_status;
}
nvals = sscanf(response, "%lf", &motorVmax);
if (nvals != 1) {
return pC_->errMsgCouldNotParseResponse("R26", response, axisNo_,
__PRETTY_FUNCTION__, __LINE__);
}
// Read out the acceleration
pl_status = pC_->read(axisNo_, 06, response);
if (pl_status != asynSuccess) {
return pl_status;
}
nvals = sscanf(response, "%lf", &motorAccel);
if (nvals != 1) {
return pC_->errMsgCouldNotParseResponse("R06", response, axisNo_,
__PRETTY_FUNCTION__, __LINE__);
}
// Cache the motor speed. If this value differs from the one in the motor
// record at the start of a movement, the motor record value is sent to
// MasterMACS.
lastSetSpeed_ = motorVelocity;
// Transform from motor to parameter library coordinates
motorPosition = motorPosition / motorRecResolution;
// Store these values in the parameter library
pl_status =
pC_->setDoubleParam(axisNo_, pC_->motorPosition_, motorPosition);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorPosition_", axisNo_,
__PRETTY_FUNCTION__, __LINE__);
}
// Write to the motor record fields
pl_status = setVeloFields(motorVelocity, 0.0, motorVmax);
if (pl_status != asynSuccess) {
return pl_status;
}
pl_status = setAcclField(motorAccel);
if (pl_status != asynSuccess) {
return pl_status;
}
pl_status = readEncoderType();
if (pl_status != asynSuccess) {
return pl_status;
}
// Update the parameter library immediately
pl_status = callParamCallbacks();
if (pl_status != asynSuccess) {
// If we can't communicate with the parameter library, it doesn't
// make sense to try and upstream this to the user -> Just log the
// error
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line "
"%d:\ncallParamCallbacks failed with %s.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
pC_->stringifyAsynStatus(pl_status));
return pl_status;
}
return pl_status;
}
// Perform the actual poll
asynStatus masterMacsAxis::doPoll(bool *moving) {
// Return value for the poll
asynStatus poll_status = asynSuccess;
// Status of read-write-operations of ASCII commands to the controller
asynStatus rw_status = asynSuccess;
// Status of parameter library operations
asynStatus pl_status = asynSuccess;
char response[pC_->MAXBUF_];
int nvals = 0;
int direction = 0;
bool hasError = false;
double currentPosition = 0.0;
double previousPosition = 0.0;
double motorRecResolution = 0.0;
double highLimit = 0.0;
double lowLimit = 0.0;
double limitsOffset = 0.0;
double handshakePerformed = 0;
// =========================================================================
// Are we currently waiting for a handshake?
if (waitForHandshake_) {
pC_->read(axisNo_, 86, response);
if (rw_status != asynSuccess) {
return rw_status;
}
nvals = sscanf(response, "%lf", &handshakePerformed);
if (nvals != 1) {
return pC_->errMsgCouldNotParseResponse(
"R86", response, axisNo_, __PRETTY_FUNCTION__, __LINE__);
}
if (handshakePerformed == 1.0) {
// Handshake has been performed successfully -> Continue with the
// poll
waitForHandshake_ = false;
} else {
// Still waiting for the handshake - try again in the next busy
// poll. This is already part of the movement procedure.
*moving = true;
pl_status = setIntegerParam(pC_->motorStatusMoving_, *moving);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status,
"motorStatusMoving_", axisNo_,
__PRETTY_FUNCTION__, __LINE__);
}
pl_status = setIntegerParam(pC_->motorStatusDone_, !(*moving));
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorStatusDone_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
return asynSuccess;
}
}
// Motor resolution from parameter library
pl_status = pC_->getDoubleParam(axisNo_, pC_->motorRecResolution_,
&motorRecResolution);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorRecResolution_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
// Read the previous motor position
pl_status =
pC_->getDoubleParam(axisNo_, pC_->motorPosition_, &previousPosition);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorPosition_", axisNo_,
__PRETTY_FUNCTION__, __LINE__);
}
// Transform from EPICS to motor coordinates (see comment in
// masterMacsAxis::readConfig())
previousPosition = previousPosition * motorRecResolution;
// Update the axis status
rw_status = readAxisStatus();
if (rw_status != asynSuccess) {
return rw_status;
}
// If the motor is switched off or if it reached its target, it is not
// moving.
if (targetReached() || !switchedOn()) {
*moving = false;
} else {
*moving = true;
}
// Read the current position
rw_status = pC_->read(axisNo_, 12, response);
if (rw_status != asynSuccess) {
return rw_status;
}
nvals = sscanf(response, "%lf", &currentPosition);
if (nvals != 1) {
return pC_->errMsgCouldNotParseResponse("R12", response, axisNo_,
__PRETTY_FUNCTION__, __LINE__);
}
/*
Read out the error if either a fault condition status flag has been set or
if a movement has just ended.
*/
msgPrintControlKey keyError = msgPrintControlKey(
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__);
if (faultConditionSet() || !(*moving)) {
rw_status = readAxisError();
/*
A communication error is a special case. If a communication between
controller and axis error occurred, all subsequent errors are ignored,
since this information is not reliable.
*/
if (communicationError()) {
if (pC_->msgPrintControl_.shouldBePrinted(keyError, true,
pC_->pasynUserSelf)) {
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line "
"%d\nCommunication error.%s\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
pC_->msgPrintControl_.getSuffix());
}
pl_status =
setStringParam(pC_->motorMessageText_,
"Communication error between PC and motor "
"controller. Please call the support.");
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorMessageText_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
poll_status = asynError;
} else {
// This buffer must be initialized to zero because we build the
// error message by appending strings.
char userMessage[pC_->MAXBUF_] = {0};
char shellMessage[pC_->MAXBUF_] = {0};
// Concatenate all other errors
if (shortCircuit()) {
appendErrorMessage(shellMessage, sizeof(shellMessage),
"Short circuit fault.");
appendErrorMessage(
userMessage, sizeof(userMessage),
"Short circuit error. Please call the support.");
poll_status = asynError;
}
if (encoderError()) {
appendErrorMessage(shellMessage, sizeof(shellMessage),
"Encoder error.");
appendErrorMessage(userMessage, sizeof(userMessage),
"Encoder error. Please call the support.");
poll_status = asynError;
}
if (followingError()) {
appendErrorMessage(
shellMessage, sizeof(shellMessage),
"Maximum callowed following error exceeded.");
appendErrorMessage(
userMessage, sizeof(userMessage),
"Maximum allowed following error exceeded.Check if "
"movement range is blocked. Otherwise please call the "
"support.");
poll_status = asynError;
}
if (feedbackError()) {
appendErrorMessage(shellMessage, sizeof(shellMessage),
"Feedback error.");
appendErrorMessage(userMessage, sizeof(userMessage),
"Feedback error. Please call the support.");
poll_status = asynError;
}
/*
Either the software limits or the end switches of the controller
have been hit. Since the EPICS limits are derived from the software
limits and are a little bit smaller, these error cases can only
happen if either the axis has an incremental encoder which is not
properly homed or if a bug occured.
*/
if (positiveLimitSwitch() || negativeLimitSwitch() ||
positiveSoftwareLimit() || negativeSoftwareLimit()) {
// Distinction for developers
if (positiveLimitSwitch()) {
appendErrorMessage(shellMessage, sizeof(shellMessage),
"Positive limit switch.");
}
if (negativeLimitSwitch()) {
appendErrorMessage(shellMessage, sizeof(shellMessage),
"Negative limit switch.");
}
if (positiveSoftwareLimit()) {
appendErrorMessage(shellMessage, sizeof(shellMessage),
"Positive software limit.");
}
if (negativeSoftwareLimit()) {
appendErrorMessage(shellMessage, sizeof(shellMessage),
"Negative software limit.");
}
// Generic error message for user
appendErrorMessage(
userMessage, sizeof(userMessage),
"Software limits or end switch hit. Try homing the motor, "
"moving in the opposite direction or check the SPS for "
"errors (if available). Otherwise please call the "
"support.");
poll_status = asynError;
}
if (overCurrent()) {
appendErrorMessage(shellMessage, sizeof(shellMessage),
"Overcurrent error.");
appendErrorMessage(
userMessage, sizeof(userMessage),
"Overcurrent error. Please call the support.");
poll_status = asynError;
}
if (overTemperature()) {
appendErrorMessage(shellMessage, sizeof(shellMessage),
"Overtemperature error.");
appendErrorMessage(
userMessage, sizeof(userMessage),
"Overtemperature error. Please call the support.");
poll_status = asynError;
}
if (overVoltage()) {
appendErrorMessage(shellMessage, sizeof(shellMessage),
"Overvoltage error.");
appendErrorMessage(
userMessage, sizeof(userMessage),
"Overvoltage error. Please call the support.");
poll_status = asynError;
}
if (underVoltage()) {
appendErrorMessage(shellMessage, sizeof(shellMessage),
"Undervoltage error.");
appendErrorMessage(
userMessage, sizeof(userMessage),
"Undervoltage error. Please call the support.");
poll_status = asynError;
}
if (stoFault()) {
appendErrorMessage(shellMessage, sizeof(shellMessage),
"STO input is on disable state.");
appendErrorMessage(userMessage, sizeof(userMessage),
"STO fault. Please call the support.");
poll_status = asynError;
}
if (strlen(shellMessage) > 0) {
if (pC_->msgPrintControl_.shouldBePrinted(keyError, true,
pC_->pasynUserSelf)) {
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line "
"%d\n%s.%s\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__,
__LINE__, shellMessage,
pC_->msgPrintControl_.getSuffix());
}
}
pl_status = setStringParam(pC_->motorMessageText_, userMessage);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorMessageText_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
}
} else {
pC_->msgPrintControl_.resetCount(keyError);
}
// Read out the limits, if the motor is not moving
if (!(*moving)) {
rw_status = pC_->read(axisNo_, 34, response);
if (rw_status != asynSuccess) {
return rw_status;
}
nvals = sscanf(response, "%lf", &lowLimit);
if (nvals != 1) {
return pC_->errMsgCouldNotParseResponse(
"R34", response, axisNo_, __PRETTY_FUNCTION__, __LINE__);
}
rw_status = pC_->read(axisNo_, 33, response);
if (rw_status != asynSuccess) {
return rw_status;
}
nvals = sscanf(response, "%lf", &highLimit);
if (nvals != 1) {
return pC_->errMsgCouldNotParseResponse(
"R33", response, axisNo_, __PRETTY_FUNCTION__, __LINE__);
}
/*
The axis limits are set as: ({[]})
where [] are the positive and negative limits set in EPICS/NICOS, {} are
the software limits set on the MCU and () are the hardware limit
switches. In other words, the EPICS/NICOS limits must be stricter than
the software limits on the MCU which in turn should be stricter than the
hardware limit switches. For example, if the hardware limit switches are
at [-10, 10], the software limits could be at [-9, 9] and the EPICS /
NICOS limits could be at
[-8, 8]. Therefore, we cannot use the software limits read from the MCU
directly, but need to shrink them a bit. In this case, we're shrinking
them by 0.1 mm or 0.1 degree (depending on the axis type) on both sides.
*/
pl_status = pC_->getDoubleParam(axisNo_, pC_->motorLimitsOffset_,
&limitsOffset);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorLimitsOffset_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
highLimit = highLimit - limitsOffset;
lowLimit = lowLimit + limitsOffset;
pl_status = pC_->setDoubleParam(axisNo_, pC_->motorHighLimitFromDriver_,
highLimit);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(
pl_status, "motorHighLimitFromDriver_", axisNo_,
__PRETTY_FUNCTION__, __LINE__);
}
pl_status = pC_->setDoubleParam(axisNo_, pC_->motorLowLimitFromDriver_,
lowLimit);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorLowLimit_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
}
// Update the enable PV
pl_status = setIntegerParam(pC_->motorEnableRBV_,
readyToBeSwitchedOn() && switchedOn());
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorEnableRBV_", axisNo_,
__PRETTY_FUNCTION__, __LINE__);
}
if (*moving) {
// If the axis is moving, evaluate the movement direction
if ((currentPosition - previousPosition) > 0) {
direction = 1;
} else {
direction = 0;
}
}
// Update the parameter library
if (hasError) {
pl_status = setIntegerParam(pC_->motorStatusProblem_, true);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorStatusProblem_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
}
pl_status = setIntegerParam(pC_->motorStatusMoving_, *moving);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorStatusMoving_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
pl_status = setIntegerParam(pC_->motorStatusDone_, !(*moving));
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorStatusDone_", axisNo_,
__PRETTY_FUNCTION__, __LINE__);
}
pl_status = setIntegerParam(pC_->motorStatusDirection_, direction);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorStatusDirection_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
// Transform from motor to EPICS coordinates (see comment in
// masterMacsAxis::init())
currentPosition = currentPosition / motorRecResolution;
pl_status = setDoubleParam(pC_->motorPosition_, currentPosition);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorPosition_", axisNo_,
__PRETTY_FUNCTION__, __LINE__);
}
return poll_status;
}
asynStatus masterMacsAxis::doMove(double position, int relative,
double minVelocity, double maxVelocity,
double acceleration) {
// Status of read-write-operations of ASCII commands to the controller
asynStatus rw_status = asynSuccess;
// Status of parameter library operations
asynStatus pl_status = asynSuccess;
char value[pC_->MAXBUF_];
double motorCoordinatesPosition = 0.0;
double motorRecResolution = 0.0;
double motorVelocity = 0.0;
int enabled = 0;
int motorCanSetSpeed = 0;
// =========================================================================
pl_status = pC_->getIntegerParam(axisNo_, pC_->motorEnableRBV_, &enabled);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "enableMotorRBV_", axisNo_,
__PRETTY_FUNCTION__, __LINE__);
}
pl_status = pC_->getDoubleParam(axisNo_, pC_->motorRecResolution_,
&motorRecResolution);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorRecResolution_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
if (enabled == 0) {
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d:\nAxis is "
"disabled.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__);
return asynSuccess;
}
// Convert from EPICS to user / motor units
motorCoordinatesPosition = position * motorRecResolution;
motorVelocity = maxVelocity * motorRecResolution;
asynPrint(
pC_->pasynUserSelf, ASYN_TRACE_FLOW,
"Controller \"%s\", axis %d => %s, line %d:\nMove to position %lf.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, position);
// Check if the speed is allowed to be changed
pl_status = pC_->getIntegerParam(axisNo_, pC_->motorCanSetSpeed_,
&motorCanSetSpeed);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorCanSetSpeed_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
// Initialize the movement handshake
rw_status = pC_->write(axisNo_, 86, "0");
if (rw_status != asynSuccess) {
pl_status = setIntegerParam(pC_->motorStatusProblem_, true);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorStatusProblem_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
return rw_status;
}
// Set the new motor speed, if the user is allowed to do so and if the
// motor speed changed since the last move command.
if (motorCanSetSpeed != 0 && lastSetSpeed_ != motorVelocity) {
lastSetSpeed_ = motorVelocity;
snprintf(value, sizeof(value), "%lf", motorVelocity);
rw_status = pC_->write(axisNo_, 05, value);
if (rw_status != asynSuccess) {
pl_status = setIntegerParam(pC_->motorStatusProblem_, true);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status,
"motorStatusProblem_", axisNo_,
__PRETTY_FUNCTION__, __LINE__);
}
return rw_status;
}
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW,
"Controller \"%s\", axis %d => %s, line %d:\nSetting speed "
"to %lf.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
motorVelocity);
}
// Set the target position
snprintf(value, sizeof(value), "%lf", motorCoordinatesPosition);
rw_status = pC_->write(axisNo_, 02, value);
if (rw_status != asynSuccess) {
pl_status = setIntegerParam(pC_->motorStatusProblem_, true);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorStatusProblem_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
return rw_status;
}
// Start the move
if (relative) {
rw_status = pC_->write(axisNo_, 00, "2");
} else {
rw_status = pC_->write(axisNo_, 00, "1");
}
if (rw_status != asynSuccess) {
pl_status = setIntegerParam(pC_->motorStatusProblem_, true);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorStatusProblem_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
return rw_status;
}
// In the next poll, we will check if the handshake has been performed in a
// reasonable time
waitForHandshake_ = true;
timeAtHandshake_ = time(NULL);
// Waiting for a handshake is already part of the movement procedure =>
// Start the watchdog
if (startMovTimeoutWatchdog() != asynSuccess) {
return asynError;
}
return rw_status;
}
asynStatus masterMacsAxis::stop(double acceleration) {
// Status of read-write-operations of ASCII commands to the controller
asynStatus rw_status = asynSuccess;
// Status of parameter library operations
asynStatus pl_status = asynSuccess;
// =========================================================================
rw_status = pC_->write(axisNo_, 00, "8");
if (rw_status != asynSuccess) {
pl_status = setIntegerParam(pC_->motorStatusProblem_, true);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorStatusProblem_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
}
return rw_status;
}
/*
Home the axis. On absolute encoder systems, this is a no-op
*/
asynStatus masterMacsAxis::doHome(double min_velocity, double max_velocity,
double acceleration, int forwards) {
// Status of read-write-operations of ASCII commands to the controller
asynStatus rw_status = asynSuccess;
// Status of parameter library operations
asynStatus pl_status = asynSuccess;
char response[pC_->MAXBUF_];
// =========================================================================
pl_status = pC_->getStringParam(axisNo_, pC_->encoderType_,
sizeof(response), response);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "encoderType_", axisNo_,
__PRETTY_FUNCTION__, __LINE__);
}
// Only send the home command if the axis has an incremental encoder
if (strcmp(response, IncrementalEncoder) == 0) {
// Initialize the movement handshake
rw_status = pC_->write(axisNo_, 86, "0");
if (rw_status != asynSuccess) {
pl_status = setIntegerParam(pC_->motorStatusProblem_, true);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status,
"motorStatusProblem_", axisNo_,
__PRETTY_FUNCTION__, __LINE__);
}
return rw_status;
}
rw_status = pC_->write(axisNo_, 00, "9");
if (rw_status != asynSuccess) {
return rw_status;
}
// In the next poll, we will check if the handshake has been performed
// in a reasonable time
waitForHandshake_ = true;
timeAtHandshake_ = time(NULL);
return asynSuccess;
} else {
return asynError;
}
}
/*
Read the encoder type and update the parameter library accordingly
*/
asynStatus masterMacsAxis::readEncoderType() {
// Status of read-write-operations of ASCII commands to the controller
asynStatus rw_status = asynSuccess;
// Status of parameter library operations
asynStatus pl_status = asynSuccess;
char command[pC_->MAXBUF_], response[pC_->MAXBUF_];
int nvals = 0;
int encoder_id = 0;
// =========================================================================
// Check if this is an absolute encoder
rw_status = pC_->read(axisNo_, 60, response);
if (rw_status != asynSuccess) {
return rw_status;
}
nvals = sscanf(response, "%d", &encoder_id);
if (nvals != 1) {
return pC_->errMsgCouldNotParseResponse(command, response, axisNo_,
__PRETTY_FUNCTION__, __LINE__);
}
/*
Defined encoder IDs:
0=INC (Incremental)
1=SSI (Absolute encoder with SSI interface)
2=SSI (Absolute encoder with BiSS interface)
*/
if (encoder_id == 0) {
pl_status = setStringParam(pC_->encoderType_, IncrementalEncoder);
} else {
pl_status = setStringParam(pC_->encoderType_, AbsoluteEncoder);
}
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "encoderType_", axisNo_,
__PRETTY_FUNCTION__, __LINE__);
}
return asynSuccess;
}
asynStatus masterMacsAxis::enable(bool on) {
int timeout_enable_disable = 2;
char value[pC_->MAXBUF_];
// Status of read-write-operations of ASCII commands to the controller
asynStatus rw_status = asynSuccess;
// Status of parameter library operations
asynStatus pl_status = asynSuccess;
bool moving = false;
// =========================================================================
doPoll(&moving);
// If the axis is currently moving, it cannot be disabled. Ignore the
// command and inform the user. We check the last known status of the
// axis instead of "moving", since status -6 is also moving, but the
// motor can actually be disabled in this state!
if (moving) {
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d:\nAxis is not "
"idle and can therefore not be disabled.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__);
pl_status =
setStringParam(pC_->motorMessageText_,
"Axis cannot be disabled while it is moving.");
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorMessageText_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
return asynError;
}
// Axis is already enabled / disabled and a new enable / disable command
// was sent => Do nothing
if ((readyToBeSwitchedOn() && switchedOn()) == on) {
asynPrint(
pC_->pasynUserSelf, ASYN_TRACE_WARNING,
"Controller \"%s\", axis %d => %s, line %d:\nAxis is already %s.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
on ? "enabled" : "disabled");
return asynSuccess;
}
// Enable / disable the axis if it is not moving
snprintf(value, sizeof(value), "%d", on);
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW,
"Controller \"%s\", axis %d => %s, line %d:\n%s axis.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
on ? "Enable" : "Disable");
if (on == 0) {
pl_status = setStringParam(pC_->motorMessageText_, "Disabling ...");
} else {
pl_status = setStringParam(pC_->motorMessageText_, "Enabling ...");
}
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorMessageText_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
rw_status = pC_->write(axisNo_, 04, value);
if (rw_status != asynSuccess) {
return rw_status;
}
// Query the axis status every few milliseconds until the axis has been
// enabled or until the timeout has been reached
int startTime = time(NULL);
while (time(NULL) < startTime + timeout_enable_disable) {
// Read the axis status
usleep(100000);
rw_status = readAxisStatus();
if (rw_status != asynSuccess) {
return rw_status;
}
if (switchedOn() == on) {
bool moving = false;
// Perform a poll to update the parameter library
poll(&moving);
return asynSuccess;
}
}
// Failed to change axis status within timeout_enable_disable => Send a
// corresponding message
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d:\nFailed to %s axis "
"within %d seconds\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
on ? "enable" : "disable", timeout_enable_disable);
// Output message to user
snprintf(value, sizeof(value), "Failed to %s within %d seconds",
on ? "enable" : "disable", timeout_enable_disable);
pl_status = setStringParam(pC_->motorMessageText_, "Enabling ...");
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorMessageText_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
return asynError;
}
/**
Convert a float to an unint16_t bitset
*/
std::bitset<16> toBitset(float val) {
// Convert the 32-bit float to an uint32_t. This works independently of the
// endianess of the system.
uint32_t temp = (uint32_t)val;
// Take the bottom half of bits to convert the 32 bit uint to a 16 bit uint
uint16_t lowerHalf = (uint16_t)(temp & 0xFFFF);
return std::bitset<16>(lowerHalf);
}
asynStatus masterMacsAxis::readAxisStatus() {
char response[pC_->MAXBUF_];
// =========================================================================
asynStatus rw_status = pC_->read(axisNo_, 10, response);
if (rw_status == asynSuccess) {
float axisStatus = 0;
int nvals = sscanf(response, "%f", &axisStatus);
if (nvals != 1) {
return pC_->errMsgCouldNotParseResponse(
"R10", response, axisNo_, __PRETTY_FUNCTION__, __LINE__);
}
axisStatus_ = toBitset(axisStatus);
}
return rw_status;
}
asynStatus masterMacsAxis::readAxisError() {
char response[pC_->MAXBUF_];
// =========================================================================
asynStatus rw_status = pC_->read(axisNo_, 11, response);
if (rw_status == asynSuccess) {
float axisError = 0;
int nvals = sscanf(response, "%f", &axisError);
if (nvals != 1) {
return pC_->errMsgCouldNotParseResponse(
"R11", response, axisNo_, __PRETTY_FUNCTION__, __LINE__);
}
axisError_ = toBitset(axisError);
}
return rw_status;
}
/***************************************************************************/
/** The following functions are C-wrappers, and can be called directly from
* iocsh */
extern "C" {
/*
C wrapper for the axis constructor. Please refer to the masterMacsAxis
constructor documentation. The controller is read from the portName.
*/
asynStatus masterMacsCreateAxis(const char *portName, int axis) {
/*
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
*/
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("Controller \"%s\" => %s, line %d:\nPort %s not found.",
portName, __PRETTY_FUNCTION__, __LINE__, portName);
return asynError;
}
// Unsafe cast of the pointer to an asynPortDriver
asynPortDriver *apd = (asynPortDriver *)(ptr);
// Safe downcast
masterMacsController *pC = dynamic_cast<masterMacsController *>(apd);
if (pC == nullptr) {
errlogPrintf("Controller \"%s\" => %s, line %d:\nController is not a "
"masterMacsController.",
portName, __PRETTY_FUNCTION__, __LINE__);
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. 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"
masterMacsAxis *pAxis = new masterMacsAxis(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
/*
Same procedure as for the CreateController function, but for the axis
itself.
*/
static const iocshArg CreateAxisArg0 = {"Controller name (e.g. mmacs1)",
iocshArgString};
static const iocshArg CreateAxisArg1 = {"Axis number", iocshArgInt};
static const iocshArg *const CreateAxisArgs[] = {&CreateAxisArg0,
&CreateAxisArg1};
static const iocshFuncDef configMasterMacsCreateAxis = {"masterMacsAxis", 2,
CreateAxisArgs};
static void configMasterMacsCreateAxisCallFunc(const iocshArgBuf *args) {
masterMacsCreateAxis(args[0].sval, args[1].ival);
}
// This function is made known to EPICS in masterMacs.dbd and is called by
// EPICS in order to register both functions in the IOC shell
static void masterMacsAxisRegister(void) {
iocshRegister(&configMasterMacsCreateAxis,
configMasterMacsCreateAxisCallFunc);
}
epicsExportRegistrar(masterMacsAxisRegister);
#endif
} // extern "C"