WIP version of the MasterMACS driver
This commit is contained in:
4
Makefile
4
Makefile
@ -11,7 +11,7 @@ REQUIRED+=asynMotor
|
|||||||
REQUIRED+=sinqMotor
|
REQUIRED+=sinqMotor
|
||||||
|
|
||||||
# Specify the version of sinqMotor we want to build against
|
# Specify the version of sinqMotor we want to build against
|
||||||
sinqMotor_VERSION=mathis_s
|
sinqMotor_VERSION=0.6.3
|
||||||
|
|
||||||
# These headers allow to depend on this library for derived drivers.
|
# These headers allow to depend on this library for derived drivers.
|
||||||
HEADERS += src/masterMacsAxis.h
|
HEADERS += src/masterMacsAxis.h
|
||||||
@ -24,4 +24,4 @@ SOURCES += src/masterMacsController.cpp
|
|||||||
# This file registers the motor-specific functions in the IOC shell.
|
# This file registers the motor-specific functions in the IOC shell.
|
||||||
DBDS += src/masterMacs.dbd
|
DBDS += src/masterMacs.dbd
|
||||||
|
|
||||||
USR_CFLAGS += -Wall -Wextra -Weffc++ -Wunused-result # -Werror
|
USR_CFLAGS += -Wall -Wextra -Weffc++ -Wunused-result -Wpedantic -Wextra -Werror
|
||||||
|
@ -40,9 +40,8 @@ masterMacsAxis::masterMacsAxis(masterMacsController *pC, int axisNo)
|
|||||||
axisStatus_ = std::bitset<16>(0);
|
axisStatus_ = std::bitset<16>(0);
|
||||||
axisError_ = std::bitset<16>(0);
|
axisError_ = std::bitset<16>(0);
|
||||||
|
|
||||||
// Default values for the watchdog timeout mechanism
|
// Initial value for the motor speed, is overwritten in atFirstPoll.
|
||||||
offsetMovTimeout_ = 30; // seconds
|
lastSetSpeed_ = 0.0;
|
||||||
scaleMovTimeout_ = 2.0;
|
|
||||||
|
|
||||||
// masterMacs motors can always be disabled
|
// masterMacs motors can always be disabled
|
||||||
status = pC_->setIntegerParam(axisNo_, pC_->motorCanDisable_, 1);
|
status = pC_->setIntegerParam(axisNo_, pC_->motorCanDisable_, 1);
|
||||||
@ -84,7 +83,7 @@ asynStatus masterMacsAxis::atFirstPoll() {
|
|||||||
|
|
||||||
// Local variable declaration
|
// Local variable declaration
|
||||||
asynStatus pl_status = asynSuccess;
|
asynStatus pl_status = asynSuccess;
|
||||||
char command[pC_->MAXBUF_], response[pC_->MAXBUF_];
|
char response[pC_->MAXBUF_];
|
||||||
int nvals = 0;
|
int nvals = 0;
|
||||||
double motorRecResolution = 0.0;
|
double motorRecResolution = 0.0;
|
||||||
double motorPosition = 0.0;
|
double motorPosition = 0.0;
|
||||||
@ -104,56 +103,54 @@ asynStatus masterMacsAxis::atFirstPoll() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Read out the current position
|
// Read out the current position
|
||||||
snprintf(command, sizeof(command), "%dR12", axisNo_);
|
pl_status = pC_->read(axisNo_, 12, response);
|
||||||
pl_status = pC_->writeRead(axisNo_, command, response, true);
|
|
||||||
if (pl_status != asynSuccess) {
|
if (pl_status != asynSuccess) {
|
||||||
return pl_status;
|
return pl_status;
|
||||||
}
|
}
|
||||||
nvals = sscanf(response, "%lf", &motorPosition);
|
nvals = sscanf(response, "%lf", &motorPosition);
|
||||||
if (nvals != 1) {
|
if (nvals != 1) {
|
||||||
return pC_->errMsgCouldNotParseResponse(command, response, axisNo_,
|
return pC_->errMsgCouldNotParseResponse("R12", response, axisNo_,
|
||||||
__PRETTY_FUNCTION__, __LINE__);
|
__PRETTY_FUNCTION__, __LINE__);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read out the current velocity
|
// Read out the current velocity
|
||||||
snprintf(command, sizeof(command), "%dR05", axisNo_);
|
pl_status = pC_->read(axisNo_, 05, response);
|
||||||
pl_status = pC_->writeRead(axisNo_, command, response, true);
|
|
||||||
if (pl_status != asynSuccess) {
|
if (pl_status != asynSuccess) {
|
||||||
return pl_status;
|
return pl_status;
|
||||||
}
|
}
|
||||||
nvals = sscanf(response, "%lf", &motorVelocity);
|
nvals = sscanf(response, "%lf", &motorVelocity);
|
||||||
if (nvals != 1) {
|
if (nvals != 1) {
|
||||||
return pC_->errMsgCouldNotParseResponse(command, response, axisNo_,
|
return pC_->errMsgCouldNotParseResponse("R05", response, axisNo_,
|
||||||
__PRETTY_FUNCTION__, __LINE__);
|
__PRETTY_FUNCTION__, __LINE__);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read out the maximum velocity
|
// Read out the maximum velocity
|
||||||
// snprintf(command, sizeof(command), "%dR26", axisNo_);
|
pl_status = pC_->read(axisNo_, 26, response);
|
||||||
// pl_status = pC_->writeRead(axisNo_, command, response, true);
|
if (pl_status != asynSuccess) {
|
||||||
// if (pl_status != asynSuccess) {
|
return pl_status;
|
||||||
// return pl_status;
|
}
|
||||||
// }
|
nvals = sscanf(response, "%lf", &motorVmax);
|
||||||
// nvals = sscanf(response, "%lf", &motorVmax);
|
if (nvals != 1) {
|
||||||
// if (nvals != 1) {
|
return pC_->errMsgCouldNotParseResponse("R26", response, axisNo_,
|
||||||
// return pC_->errMsgCouldNotParseResponse(command, response, axisNo_,
|
__PRETTY_FUNCTION__, __LINE__);
|
||||||
// __PRETTY_FUNCTION__,
|
}
|
||||||
// __LINE__);
|
|
||||||
// }
|
|
||||||
// TODO: Temporary workaround until R26 is implemented
|
|
||||||
motorVmax = motorVelocity;
|
|
||||||
|
|
||||||
// Read out the acceleration
|
// Read out the acceleration
|
||||||
snprintf(command, sizeof(command), "%dR06", axisNo_);
|
pl_status = pC_->read(axisNo_, 06, response);
|
||||||
pl_status = pC_->writeRead(axisNo_, command, response, true);
|
|
||||||
if (pl_status != asynSuccess) {
|
if (pl_status != asynSuccess) {
|
||||||
return pl_status;
|
return pl_status;
|
||||||
}
|
}
|
||||||
nvals = sscanf(response, "%lf", &motorAccel);
|
nvals = sscanf(response, "%lf", &motorAccel);
|
||||||
if (nvals != 1) {
|
if (nvals != 1) {
|
||||||
return pC_->errMsgCouldNotParseResponse(command, response, axisNo_,
|
return pC_->errMsgCouldNotParseResponse("R06", response, axisNo_,
|
||||||
__PRETTY_FUNCTION__, __LINE__);
|
__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
|
// Transform from motor to parameter library coordinates
|
||||||
motorPosition = motorPosition / motorRecResolution;
|
motorPosition = motorPosition / motorRecResolution;
|
||||||
|
|
||||||
@ -208,7 +205,7 @@ asynStatus masterMacsAxis::doPoll(bool *moving) {
|
|||||||
// Status of parameter library operations
|
// Status of parameter library operations
|
||||||
asynStatus pl_status = asynSuccess;
|
asynStatus pl_status = asynSuccess;
|
||||||
|
|
||||||
char command[pC_->MAXBUF_], response[pC_->MAXBUF_];
|
char response[pC_->MAXBUF_];
|
||||||
int nvals = 0;
|
int nvals = 0;
|
||||||
|
|
||||||
int direction = 0;
|
int direction = 0;
|
||||||
@ -219,7 +216,6 @@ asynStatus masterMacsAxis::doPoll(bool *moving) {
|
|||||||
double highLimit = 0.0;
|
double highLimit = 0.0;
|
||||||
double lowLimit = 0.0;
|
double lowLimit = 0.0;
|
||||||
double limitsOffset = 0.0;
|
double limitsOffset = 0.0;
|
||||||
int wasMoving = 0;
|
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
||||||
@ -249,86 +245,113 @@ asynStatus masterMacsAxis::doPoll(bool *moving) {
|
|||||||
return rw_status;
|
return rw_status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (faultConditionSet()) {
|
||||||
|
rw_status = readAxisError();
|
||||||
|
|
||||||
|
// TODO: Handling
|
||||||
|
|
||||||
|
// pl_status =
|
||||||
|
// pC_->getIntegerParam(axisNo_, pC_->motorStatusMoving_,
|
||||||
|
// &wasMoving);
|
||||||
|
// if (pl_status != asynSuccess) {
|
||||||
|
// return pC_->paramLibAccessFailed(pl_status, "motorStatusMoving_",
|
||||||
|
// __PRETTY_FUNCTION__, __LINE__);
|
||||||
|
// }
|
||||||
|
// if (!isMoving() && wasMoving == 1) {
|
||||||
|
// rw_status = readAxisError();
|
||||||
|
// if (rw_status != asynSuccess) {
|
||||||
|
// return rw_status;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // TODO> Error handling
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
// Update the movement status
|
// Update the movement status
|
||||||
*moving = isMoving();
|
*moving = isMoving();
|
||||||
|
|
||||||
|
if (*moving) {
|
||||||
|
|
||||||
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
|
"%s => line %d:\nMOVMOVMOV\n", __PRETTY_FUNCTION__, __LINE__);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enabled()) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// Read the current position
|
// Read the current position
|
||||||
snprintf(command, sizeof(command), "%dR12", axisNo_);
|
rw_status = pC_->read(axisNo_, 12, response);
|
||||||
rw_status = pC_->writeRead(axisNo_, command, response, true);
|
|
||||||
if (rw_status != asynSuccess) {
|
if (rw_status != asynSuccess) {
|
||||||
return rw_status;
|
return rw_status;
|
||||||
}
|
}
|
||||||
nvals = sscanf(response, "%lf", ¤tPosition);
|
nvals = sscanf(response, "%lf", ¤tPosition);
|
||||||
if (nvals != 1) {
|
if (nvals != 1) {
|
||||||
return pC_->errMsgCouldNotParseResponse(command, response, axisNo_,
|
return pC_->errMsgCouldNotParseResponse("R12", response, axisNo_,
|
||||||
__PRETTY_FUNCTION__, __LINE__);
|
__PRETTY_FUNCTION__, __LINE__);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the low limit
|
// Read out the limits, if the motor is not moving
|
||||||
snprintf(command, sizeof(command), "%dR34", axisNo_);
|
if (!isMoving()) {
|
||||||
rw_status = pC_->writeRead(axisNo_, command, response, true);
|
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(command, response, axisNo_,
|
|
||||||
__PRETTY_FUNCTION__, __LINE__);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the high limit
|
|
||||||
snprintf(command, sizeof(command), "%dR33", axisNo_);
|
|
||||||
rw_status = pC_->writeRead(axisNo_, command, response, true);
|
|
||||||
if (rw_status != asynSuccess) {
|
|
||||||
return rw_status;
|
|
||||||
}
|
|
||||||
nvals = sscanf(response, "%lf", &highLimit);
|
|
||||||
if (nvals != 1) {
|
|
||||||
return pC_->errMsgCouldNotParseResponse(command, response, axisNo_,
|
|
||||||
__PRETTY_FUNCTION__, __LINE__);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Did we just finish a movement? If yes, read out the error.
|
|
||||||
pl_status =
|
|
||||||
pC_->getIntegerParam(axisNo_, pC_->motorStatusMoving_, &wasMoving);
|
|
||||||
if (pl_status != asynSuccess) {
|
|
||||||
return pC_->paramLibAccessFailed(pl_status, "motorStatusMoving_",
|
|
||||||
__PRETTY_FUNCTION__, __LINE__);
|
|
||||||
}
|
|
||||||
if (!isMoving() && wasMoving == 1) {
|
|
||||||
rw_status = readAxisError();
|
|
||||||
if (rw_status != asynSuccess) {
|
if (rw_status != asynSuccess) {
|
||||||
return rw_status;
|
return rw_status;
|
||||||
}
|
}
|
||||||
|
nvals = sscanf(response, "%lf", &lowLimit);
|
||||||
|
if (nvals != 1) {
|
||||||
|
return pC_->errMsgCouldNotParseResponse(
|
||||||
|
"R34", response, axisNo_, __PRETTY_FUNCTION__, __LINE__);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO> Error handling
|
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_",
|
||||||
|
__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_",
|
||||||
|
__PRETTY_FUNCTION__, __LINE__);
|
||||||
|
}
|
||||||
|
|
||||||
|
pl_status = pC_->setDoubleParam(axisNo_, pC_->motorLowLimitFromDriver_,
|
||||||
|
lowLimit);
|
||||||
|
if (pl_status != asynSuccess) {
|
||||||
|
return pC_->paramLibAccessFailed(pl_status, "motorLowLimit_",
|
||||||
|
__PRETTY_FUNCTION__, __LINE__);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Update the enable PV
|
||||||
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_",
|
|
||||||
__PRETTY_FUNCTION__, __LINE__);
|
|
||||||
}
|
|
||||||
highLimit = highLimit - limitsOffset;
|
|
||||||
lowLimit = lowLimit + limitsOffset;
|
|
||||||
|
|
||||||
//
|
|
||||||
|
|
||||||
// Update the enablement PV
|
|
||||||
pl_status = setIntegerParam(pC_->motorEnableRBV_, enabled());
|
pl_status = setIntegerParam(pC_->motorEnableRBV_, enabled());
|
||||||
if (pl_status != asynSuccess) {
|
if (pl_status != asynSuccess) {
|
||||||
return pC_->paramLibAccessFailed(pl_status, "motorEnableRBV_",
|
return pC_->paramLibAccessFailed(pl_status, "motorEnableRBV_",
|
||||||
@ -379,20 +402,6 @@ asynStatus masterMacsAxis::doPoll(bool *moving) {
|
|||||||
__PRETTY_FUNCTION__, __LINE__);
|
__PRETTY_FUNCTION__, __LINE__);
|
||||||
}
|
}
|
||||||
|
|
||||||
pl_status =
|
|
||||||
pC_->setDoubleParam(axisNo_, pC_->motorHighLimitFromDriver_, highLimit);
|
|
||||||
if (pl_status != asynSuccess) {
|
|
||||||
return pC_->paramLibAccessFailed(pl_status, "motorHighLimitFromDriver_",
|
|
||||||
__PRETTY_FUNCTION__, __LINE__);
|
|
||||||
}
|
|
||||||
|
|
||||||
pl_status =
|
|
||||||
pC_->setDoubleParam(axisNo_, pC_->motorLowLimitFromDriver_, lowLimit);
|
|
||||||
if (pl_status != asynSuccess) {
|
|
||||||
return pC_->paramLibAccessFailed(pl_status, "motorLowLimit_",
|
|
||||||
__PRETTY_FUNCTION__, __LINE__);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transform from motor to EPICS coordinates (see comment in
|
// Transform from motor to EPICS coordinates (see comment in
|
||||||
// masterMacsAxis::atFirstPoll())
|
// masterMacsAxis::atFirstPoll())
|
||||||
currentPosition = currentPosition / motorRecResolution;
|
currentPosition = currentPosition / motorRecResolution;
|
||||||
@ -416,7 +425,7 @@ asynStatus masterMacsAxis::doMove(double position, int relative,
|
|||||||
// Status of parameter library operations
|
// Status of parameter library operations
|
||||||
asynStatus pl_status = asynSuccess;
|
asynStatus pl_status = asynSuccess;
|
||||||
|
|
||||||
char command[pC_->MAXBUF_], response[pC_->MAXBUF_];
|
char value[pC_->MAXBUF_];
|
||||||
double motorCoordinatesPosition = 0.0;
|
double motorCoordinatesPosition = 0.0;
|
||||||
double motorRecResolution = 0.0;
|
double motorRecResolution = 0.0;
|
||||||
double motorVelocity = 0.0;
|
double motorVelocity = 0.0;
|
||||||
@ -461,10 +470,14 @@ asynStatus masterMacsAxis::doMove(double position, int relative,
|
|||||||
__PRETTY_FUNCTION__, __LINE__);
|
__PRETTY_FUNCTION__, __LINE__);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the new motor speed, if the user is allowed to do so.
|
// Set the new motor speed, if the user is allowed to do so and if the motor
|
||||||
if (motorCanSetSpeed != 0) {
|
// speed changed since the last move command.
|
||||||
snprintf(command, sizeof(command), "%dS05=%lf", axisNo_, motorVelocity);
|
if (motorCanSetSpeed != 0 && lastSetSpeed_ != motorVelocity) {
|
||||||
rw_status = pC_->writeRead(axisNo_, command, response, false);
|
|
||||||
|
lastSetSpeed_ = motorVelocity;
|
||||||
|
|
||||||
|
snprintf(value, sizeof(value), "%lf", motorVelocity);
|
||||||
|
rw_status = pC_->write(axisNo_, 05, value);
|
||||||
if (rw_status != asynSuccess) {
|
if (rw_status != asynSuccess) {
|
||||||
pl_status = setIntegerParam(pC_->motorStatusProblem_, true);
|
pl_status = setIntegerParam(pC_->motorStatusProblem_, true);
|
||||||
if (pl_status != asynSuccess) {
|
if (pl_status != asynSuccess) {
|
||||||
@ -481,9 +494,8 @@ asynStatus masterMacsAxis::doMove(double position, int relative,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set the target position
|
// Set the target position
|
||||||
snprintf(command, sizeof(command), "%dS02=%lf", axisNo_,
|
snprintf(value, sizeof(value), "%lf", motorCoordinatesPosition);
|
||||||
motorCoordinatesPosition);
|
rw_status = pC_->write(axisNo_, 02, value);
|
||||||
rw_status = pC_->writeRead(axisNo_, command, response, false);
|
|
||||||
if (rw_status != asynSuccess) {
|
if (rw_status != asynSuccess) {
|
||||||
pl_status = setIntegerParam(pC_->motorStatusProblem_, true);
|
pl_status = setIntegerParam(pC_->motorStatusProblem_, true);
|
||||||
if (pl_status != asynSuccess) {
|
if (pl_status != asynSuccess) {
|
||||||
@ -495,11 +507,10 @@ asynStatus masterMacsAxis::doMove(double position, int relative,
|
|||||||
|
|
||||||
// Start the move
|
// Start the move
|
||||||
if (relative) {
|
if (relative) {
|
||||||
snprintf(command, sizeof(command), "%dS00=2", axisNo_);
|
rw_status = pC_->write(axisNo_, 00, "2");
|
||||||
} else {
|
} else {
|
||||||
snprintf(command, sizeof(command), "%dS00=1", axisNo_);
|
rw_status = pC_->write(axisNo_, 00, "1");
|
||||||
}
|
}
|
||||||
rw_status = pC_->writeRead(axisNo_, command, response, false);
|
|
||||||
if (rw_status != asynSuccess) {
|
if (rw_status != asynSuccess) {
|
||||||
pl_status = setIntegerParam(pC_->motorStatusProblem_, true);
|
pl_status = setIntegerParam(pC_->motorStatusProblem_, true);
|
||||||
if (pl_status != asynSuccess) {
|
if (pl_status != asynSuccess) {
|
||||||
@ -526,12 +537,9 @@ asynStatus masterMacsAxis::stop(double acceleration) {
|
|||||||
// Status of parameter library operations
|
// Status of parameter library operations
|
||||||
asynStatus pl_status = asynSuccess;
|
asynStatus pl_status = asynSuccess;
|
||||||
|
|
||||||
char command[pC_->MAXBUF_], response[pC_->MAXBUF_];
|
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
||||||
snprintf(command, sizeof(command), "%dS00=8", axisNo_);
|
rw_status = pC_->write(axisNo_, 00, "8");
|
||||||
rw_status = pC_->writeRead(axisNo_, command, response, false);
|
|
||||||
if (rw_status != asynSuccess) {
|
if (rw_status != asynSuccess) {
|
||||||
pl_status = setIntegerParam(pC_->motorStatusProblem_, true);
|
pl_status = setIntegerParam(pC_->motorStatusProblem_, true);
|
||||||
if (pl_status != asynSuccess) {
|
if (pl_status != asynSuccess) {
|
||||||
@ -555,7 +563,7 @@ asynStatus masterMacsAxis::doHome(double min_velocity, double max_velocity,
|
|||||||
// Status of parameter library operations
|
// Status of parameter library operations
|
||||||
asynStatus pl_status = asynSuccess;
|
asynStatus pl_status = asynSuccess;
|
||||||
|
|
||||||
char command[pC_->MAXBUF_], response[pC_->MAXBUF_];
|
char response[pC_->MAXBUF_];
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
||||||
@ -569,8 +577,7 @@ asynStatus masterMacsAxis::doHome(double min_velocity, double max_velocity,
|
|||||||
// Only send the home command if the axis has an incremental encoder
|
// Only send the home command if the axis has an incremental encoder
|
||||||
if (strcmp(response, IncrementalEncoder) == 0) {
|
if (strcmp(response, IncrementalEncoder) == 0) {
|
||||||
|
|
||||||
snprintf(command, sizeof(command), "%dS00=9", axisNo_);
|
rw_status = pC_->write(axisNo_, 00, "9");
|
||||||
rw_status = pC_->writeRead(axisNo_, command, response, false);
|
|
||||||
if (rw_status != asynSuccess) {
|
if (rw_status != asynSuccess) {
|
||||||
return rw_status;
|
return rw_status;
|
||||||
}
|
}
|
||||||
@ -616,8 +623,7 @@ asynStatus masterMacsAxis::readEncoderType() {
|
|||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
||||||
// Check if this is an absolute encoder
|
// Check if this is an absolute encoder
|
||||||
snprintf(command, sizeof(command), "%dR60", axisNo_);
|
rw_status = pC_->read(axisNo_, 60, response);
|
||||||
rw_status = pC_->writeRead(axisNo_, command, response, true);
|
|
||||||
if (rw_status != asynSuccess) {
|
if (rw_status != asynSuccess) {
|
||||||
return rw_status;
|
return rw_status;
|
||||||
}
|
}
|
||||||
@ -650,7 +656,7 @@ asynStatus masterMacsAxis::readEncoderType() {
|
|||||||
asynStatus masterMacsAxis::enable(bool on) {
|
asynStatus masterMacsAxis::enable(bool on) {
|
||||||
|
|
||||||
int timeout_enable_disable = 2;
|
int timeout_enable_disable = 2;
|
||||||
char command[pC_->MAXBUF_], response[pC_->MAXBUF_];
|
char value[pC_->MAXBUF_];
|
||||||
|
|
||||||
// Status of read-write-operations of ASCII commands to the controller
|
// Status of read-write-operations of ASCII commands to the controller
|
||||||
asynStatus rw_status = asynSuccess;
|
asynStatus rw_status = asynSuccess;
|
||||||
@ -659,10 +665,11 @@ asynStatus masterMacsAxis::enable(bool on) {
|
|||||||
asynStatus pl_status = asynSuccess;
|
asynStatus pl_status = asynSuccess;
|
||||||
|
|
||||||
bool moving = false;
|
bool moving = false;
|
||||||
doPoll(&moving);
|
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
||||||
|
doPoll(&moving);
|
||||||
|
|
||||||
// If the axis is currently moving, it cannot be disabled. Ignore the
|
// 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
|
// 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
|
// axis instead of "moving", since status -6 is also moving, but the
|
||||||
@ -686,7 +693,7 @@ asynStatus masterMacsAxis::enable(bool on) {
|
|||||||
|
|
||||||
// Axis is already enabled / disabled and a new enable / disable command
|
// Axis is already enabled / disabled and a new enable / disable command
|
||||||
// was sent => Do nothing
|
// was sent => Do nothing
|
||||||
if ((axisStatus_ != -3) == on) {
|
if (enabled() == on) {
|
||||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_WARNING,
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_WARNING,
|
||||||
"%s => line %d:\nAxis %d on controller %s is already %s.\n",
|
"%s => line %d:\nAxis %d on controller %s is already %s.\n",
|
||||||
__PRETTY_FUNCTION__, __LINE__, axisNo_, pC_->portName,
|
__PRETTY_FUNCTION__, __LINE__, axisNo_, pC_->portName,
|
||||||
@ -695,7 +702,7 @@ asynStatus masterMacsAxis::enable(bool on) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Enable / disable the axis if it is not moving
|
// Enable / disable the axis if it is not moving
|
||||||
snprintf(command, sizeof(command), "%dS04=%d", axisNo_, on);
|
snprintf(value, sizeof(value), "%d", on);
|
||||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW,
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW,
|
||||||
"%s => line %d:\n%s axis %d on controller %s\n",
|
"%s => line %d:\n%s axis %d on controller %s\n",
|
||||||
__PRETTY_FUNCTION__, __LINE__, on ? "Enable" : "Disable", axisNo_,
|
__PRETTY_FUNCTION__, __LINE__, on ? "Enable" : "Disable", axisNo_,
|
||||||
@ -709,7 +716,7 @@ asynStatus masterMacsAxis::enable(bool on) {
|
|||||||
return pC_->paramLibAccessFailed(pl_status, "motorMessageText_",
|
return pC_->paramLibAccessFailed(pl_status, "motorMessageText_",
|
||||||
__PRETTY_FUNCTION__, __LINE__);
|
__PRETTY_FUNCTION__, __LINE__);
|
||||||
}
|
}
|
||||||
rw_status = pC_->writeRead(axisNo_, command, response, false);
|
rw_status = pC_->write(axisNo_, 04, value);
|
||||||
if (rw_status != asynSuccess) {
|
if (rw_status != asynSuccess) {
|
||||||
return rw_status;
|
return rw_status;
|
||||||
}
|
}
|
||||||
@ -736,14 +743,14 @@ asynStatus masterMacsAxis::enable(bool on) {
|
|||||||
|
|
||||||
// Failed to change axis status within timeout_enable_disable => Send a
|
// Failed to change axis status within timeout_enable_disable => Send a
|
||||||
// corresponding message
|
// corresponding message
|
||||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW,
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
"%s => line %d:\nFailed to %s axis %d on controller %s within %d "
|
"%s => line %d:\nFailed to %s axis %d on controller %s within %d "
|
||||||
"seconds\n",
|
"seconds\n",
|
||||||
__PRETTY_FUNCTION__, __LINE__, on ? "enable" : "disable", axisNo_,
|
__PRETTY_FUNCTION__, __LINE__, on ? "enable" : "disable", axisNo_,
|
||||||
pC_->portName, timeout_enable_disable);
|
pC_->portName, timeout_enable_disable);
|
||||||
|
|
||||||
// Output message to user
|
// Output message to user
|
||||||
snprintf(command, sizeof(command), "Failed to %s within %d seconds",
|
snprintf(value, sizeof(value), "Failed to %s within %d seconds",
|
||||||
on ? "enable" : "disable", timeout_enable_disable);
|
on ? "enable" : "disable", timeout_enable_disable);
|
||||||
pl_status = setStringParam(pC_->motorMessageText_, "Enabling ...");
|
pl_status = setStringParam(pC_->motorMessageText_, "Enabling ...");
|
||||||
if (pl_status != asynSuccess) {
|
if (pl_status != asynSuccess) {
|
||||||
@ -754,19 +761,18 @@ asynStatus masterMacsAxis::enable(bool on) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
asynStatus masterMacsAxis::readAxisStatus() {
|
asynStatus masterMacsAxis::readAxisStatus() {
|
||||||
char command[pC_->MAXBUF_], response[pC_->MAXBUF_];
|
char response[pC_->MAXBUF_];
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
||||||
snprintf(command, sizeof(command), "%dR10", axisNo_);
|
asynStatus rw_status = pC_->read(axisNo_, 10, response);
|
||||||
asynStatus rw_status = pC_->writeRead(axisNo_, command, response, true);
|
|
||||||
if (rw_status == asynSuccess) {
|
if (rw_status == asynSuccess) {
|
||||||
|
|
||||||
int axisStatus = 0;
|
int axisStatus = 0;
|
||||||
int nvals = sscanf(response, "%d", &axisStatus);
|
int nvals = sscanf(response, "%d", &axisStatus);
|
||||||
if (nvals != 1) {
|
if (nvals != 1) {
|
||||||
return pC_->errMsgCouldNotParseResponse(
|
return pC_->errMsgCouldNotParseResponse(
|
||||||
command, response, axisNo_, __PRETTY_FUNCTION__, __LINE__);
|
"R10", response, axisNo_, __PRETTY_FUNCTION__, __LINE__);
|
||||||
}
|
}
|
||||||
axisStatus_ = std::bitset<16>(axisStatus);
|
axisStatus_ = std::bitset<16>(axisStatus);
|
||||||
}
|
}
|
||||||
@ -774,21 +780,20 @@ asynStatus masterMacsAxis::readAxisStatus() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
asynStatus masterMacsAxis::readAxisError() {
|
asynStatus masterMacsAxis::readAxisError() {
|
||||||
char command[pC_->MAXBUF_], response[pC_->MAXBUF_];
|
char response[pC_->MAXBUF_];
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
||||||
snprintf(command, sizeof(command), "%dR11", axisNo_);
|
asynStatus rw_status = pC_->read(axisNo_, 11, response);
|
||||||
asynStatus rw_status = pC_->writeRead(axisNo_, command, response, true);
|
|
||||||
if (rw_status == asynSuccess) {
|
if (rw_status == asynSuccess) {
|
||||||
|
|
||||||
int axisError = 0;
|
int axisError = 0;
|
||||||
int nvals = sscanf(response, "%d", &axisError);
|
int nvals = sscanf(response, "%d", &axisError);
|
||||||
if (nvals != 1) {
|
if (nvals != 1) {
|
||||||
return pC_->errMsgCouldNotParseResponse(
|
return pC_->errMsgCouldNotParseResponse(
|
||||||
command, response, axisNo_, __PRETTY_FUNCTION__, __LINE__);
|
"R11", response, axisNo_, __PRETTY_FUNCTION__, __LINE__);
|
||||||
}
|
}
|
||||||
axisError_ = std::bitset<16>(axisError);
|
axisError_ = std::bitset<16>(axisError);
|
||||||
}
|
}
|
||||||
return rw_status;
|
return rw_status;
|
||||||
}
|
}
|
||||||
|
@ -99,6 +99,7 @@ class masterMacsAxis : public sinqAxis {
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
masterMacsController *pC_;
|
masterMacsController *pC_;
|
||||||
|
double lastSetSpeed_;
|
||||||
|
|
||||||
asynStatus readConfig();
|
asynStatus readConfig();
|
||||||
bool initial_poll_;
|
bool initial_poll_;
|
||||||
|
@ -152,20 +152,32 @@ masterMacsAxis *masterMacsController::castToAxis(asynMotorAxis *asynAxis) {
|
|||||||
return axis;
|
return axis;
|
||||||
}
|
}
|
||||||
|
|
||||||
asynStatus masterMacsController::writeRead(int axisNo, const char *command,
|
asynStatus masterMacsController::read(int axisNo, int tcpCmd, char *response) {
|
||||||
char *response,
|
return writeRead(axisNo, tcpCmd, NULL, response);
|
||||||
bool expectResponse) {
|
}
|
||||||
|
|
||||||
|
asynStatus masterMacsController::write(int axisNo, int tcpCmd,
|
||||||
|
const char *payload) {
|
||||||
|
return writeRead(axisNo, tcpCmd, payload, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
asynStatus masterMacsController::writeRead(int axisNo, int tcpCmd,
|
||||||
|
const char *payload,
|
||||||
|
char *response) {
|
||||||
|
|
||||||
// Definition of local variables.
|
// Definition of local variables.
|
||||||
asynStatus status = asynSuccess;
|
asynStatus status = asynSuccess;
|
||||||
asynStatus pl_status = asynSuccess;
|
asynStatus pl_status = asynSuccess;
|
||||||
std::string fullCommand = "";
|
char fullCommand[MAXBUF_] = {0};
|
||||||
char fullResponse[MAXBUF_] = {0};
|
char fullResponse[MAXBUF_] = {0};
|
||||||
char printableCommand[MAXBUF_] = {0};
|
char printableCommand[MAXBUF_] = {0};
|
||||||
char printableResponse[MAXBUF_] = {0};
|
char printableResponse[MAXBUF_] = {0};
|
||||||
char drvMessageText[MAXBUF_] = {0};
|
char drvMessageText[MAXBUF_] = {0};
|
||||||
int motorStatusProblem = 0;
|
int motorStatusProblem = 0;
|
||||||
|
|
||||||
|
int valueStart = 0;
|
||||||
|
int valueStop = 0;
|
||||||
|
|
||||||
// Send the message and block the thread until either a response has
|
// Send the message and block the thread until either a response has
|
||||||
// been received or the timeout is triggered
|
// been received or the timeout is triggered
|
||||||
int eomReason = 0; // Flag indicating why the message has ended
|
int eomReason = 0; // Flag indicating why the message has ended
|
||||||
@ -178,11 +190,8 @@ asynStatus masterMacsController::writeRead(int axisNo, const char *command,
|
|||||||
// end-of-string terminator defined in the constructor)
|
// end-of-string terminator defined in the constructor)
|
||||||
size_t nbytesIn = 0;
|
size_t nbytesIn = 0;
|
||||||
|
|
||||||
// These parameters are used to remove not-needed information from the
|
// Do we expect an response?
|
||||||
// response.
|
bool isRead = response != NULL;
|
||||||
int dataStartIndex = 0;
|
|
||||||
int validIndex = 0;
|
|
||||||
bool responseValid = false;
|
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
||||||
@ -195,11 +204,9 @@ asynStatus masterMacsController::writeRead(int axisNo, const char *command,
|
|||||||
/*
|
/*
|
||||||
PSI SINQ uses a custom protocol which is described in
|
PSI SINQ uses a custom protocol which is described in
|
||||||
PSI_TCP_Interface_V1-8.pdf (p. // 4-17).
|
PSI_TCP_Interface_V1-8.pdf (p. // 4-17).
|
||||||
A special case is the message length, which is specified by two bytes LSB
|
A special case is the message length, which is specified by two bytes
|
||||||
and MSB:
|
LSB and MSB: MSB = message length / 256 LSB = message length % 256. For
|
||||||
MSB = message length / 256
|
example, a message length of 47 chars would result in MSB = 0, LSB = 47,
|
||||||
LSB = message length % 256.
|
|
||||||
For example, a message length of 47 chars would result in MSB = 0, LSB = 47,
|
|
||||||
whereas a message length of 356 would result in MSB = 1, LSB = 100.
|
whereas a message length of 356 would result in MSB = 1, LSB = 100.
|
||||||
|
|
||||||
The full protocol looks as follows:
|
The full protocol looks as follows:
|
||||||
@ -211,175 +218,106 @@ asynStatus masterMacsController::writeRead(int axisNo, const char *command,
|
|||||||
this protocol encodes the message length in LSB and MSB.
|
this protocol encodes the message length in LSB and MSB.
|
||||||
0x0D -> Carriage return (ASCII alias \r)
|
0x0D -> Carriage return (ASCII alias \r)
|
||||||
0x03 -> End of text ETX
|
0x03 -> End of text ETX
|
||||||
|
|
||||||
The rest of the telegram length is filled with 0x00 as specified in the
|
|
||||||
protocol.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Command has four additional bytes between ENQ and ETX:
|
fullCommand[0] = '\x05'; // ENQ
|
||||||
// LSB, MSB, PDO1 and CR
|
fullCommand[1] = 1; // Placeholder value, can be anything other than 0
|
||||||
const size_t commandLength = strlen(command) + 4;
|
fullCommand[2] = 1; // Placeholder value, can be anything other than 0
|
||||||
|
fullCommand[3] = '\x19'; // PD01
|
||||||
|
|
||||||
|
// Create the command and add CR and ETX at the end
|
||||||
|
if (isRead) {
|
||||||
|
snprintf(&fullCommand[4], MAXBUF_ - 4, "%dR%02d\x0D\x03", axisNo,
|
||||||
|
tcpCmd);
|
||||||
|
} else {
|
||||||
|
snprintf(&fullCommand[4], MAXBUF_ - 4, "%dS%02d=%s\x0D\x03", axisNo,
|
||||||
|
tcpCmd, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the command length
|
||||||
|
const size_t fullCommandLength = strlen(fullCommand);
|
||||||
|
|
||||||
|
// Length of the command without ENQ and ETX
|
||||||
|
const size_t lenWithMetadata = fullCommandLength - 2;
|
||||||
|
|
||||||
// Perform both division and modulo operation at once.
|
// Perform both division and modulo operation at once.
|
||||||
div_t commandLengthSep = std::div(commandLength, 256);
|
div_t lenWithMetadataSep = std::div(lenWithMetadata, 256);
|
||||||
|
|
||||||
/*
|
// Now set the actual command length
|
||||||
Build the actual command. LSB and MSB need to be converted directly to hex,
|
fullCommand[1] = lenWithMetadataSep.rem; // LSB
|
||||||
hence they are interpolated as characters. For example, the eight hex value
|
fullCommand[2] = lenWithMetadataSep.quot; // MSB
|
||||||
is 0x08, but interpolating 8 directly via %x or %d returns the 38th hex
|
|
||||||
value, since 8 is interpreted as ASCII in those cases.
|
|
||||||
*/
|
|
||||||
fullCommand.push_back('\x05');
|
|
||||||
fullCommand.push_back(commandLengthSep.rem);
|
|
||||||
fullCommand.push_back(commandLengthSep.quot);
|
|
||||||
fullCommand.push_back('\x19');
|
|
||||||
for (size_t i = 0; i < strlen(command); i++) {
|
|
||||||
fullCommand.push_back(command[i]);
|
|
||||||
}
|
|
||||||
fullCommand.push_back('\x0D');
|
|
||||||
fullCommand.push_back('\x03');
|
|
||||||
// snprintf(fullCommand, MAXBUF_, "\x05%c%c\x19%s\x0D\x03",
|
|
||||||
// commandLengthSep.rem, commandLengthSep.quot, command);
|
|
||||||
|
|
||||||
adjustForPrint(printableCommand, fullCommand.c_str(), MAXBUF_);
|
adjustForPrint(printableCommand, fullCommand, MAXBUF_);
|
||||||
asynPrint(this->pasynUserSelf, ASYN_TRACEIO_DRIVER,
|
asynPrint(this->pasynUserSelf, ASYN_TRACEIO_DRIVER,
|
||||||
"%s => line %d:\nSending command %s\n", __PRETTY_FUNCTION__,
|
"%s => line %d:\nSending command %s\n", __PRETTY_FUNCTION__,
|
||||||
__LINE__, printableCommand);
|
__LINE__, printableCommand);
|
||||||
|
|
||||||
// Perform the actual writeRead
|
// Send out the command
|
||||||
status = pasynOctetSyncIO->writeRead(
|
status =
|
||||||
lowLevelPortUser_, fullCommand.c_str(), fullCommand.length(),
|
pasynOctetSyncIO->write(lowLevelPortUser_, fullCommand,
|
||||||
fullResponse, MAXBUF_, comTimeout_, &nbytesOut, &nbytesIn, &eomReason);
|
fullCommandLength, comTimeout_, &nbytesOut);
|
||||||
|
|
||||||
// ___________________________________________________________DEBUG
|
if (status == asynSuccess) {
|
||||||
|
|
||||||
// asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
// Try to read the answer repeatedly
|
||||||
// "=================== COMMAND ===================\n");
|
int maxTrials = 2;
|
||||||
|
for (int i = 0; i < maxTrials; i++) {
|
||||||
|
status =
|
||||||
|
pasynOctetSyncIO->read(lowLevelPortUser_, fullResponse, MAXBUF_,
|
||||||
|
comTimeout_, &nbytesIn, &eomReason);
|
||||||
|
|
||||||
// for (int i = 0; i < 12; ++i) {
|
if (status == asynSuccess) {
|
||||||
// asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, "%x: %c\n",
|
status = parseResponse(fullCommand, fullResponse,
|
||||||
// fullCommand[i], fullCommand[i]);
|
drvMessageText, &valueStart, &valueStop,
|
||||||
// }
|
axisNo, tcpCmd, isRead);
|
||||||
// asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, "\n");
|
if (status == asynSuccess) {
|
||||||
|
// Received the correct message
|
||||||
// asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
break;
|
||||||
// "=================== RESPONSE ===================\n");
|
}
|
||||||
|
} else {
|
||||||
// for (int i = 0; i < 40; ++i) {
|
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
// asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, "%x: %c\n",
|
"%s => line %d:\nError %s while reading from the "
|
||||||
// fullResponse[i], fullResponse[i]);
|
"controller\n",
|
||||||
// }
|
__PRETTY_FUNCTION__, __LINE__,
|
||||||
// asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, "\n");
|
stringifyAsynStatus(status));
|
||||||
|
|
||||||
// ___________________________________________________________DEBUG
|
|
||||||
|
|
||||||
/*
|
|
||||||
As of 19.12.2025, there is a bug in the MasterMACS hardware: If two commands
|
|
||||||
are sent in rapid succession, MasterMACS sends garbage as answer for the
|
|
||||||
second command. Crucially, this garbage does not contain an CR. Hence, the
|
|
||||||
following strategy is implemented here:
|
|
||||||
- Wait 1 ms after a pasynOctetSyncIO->writeRead
|
|
||||||
- If the message does not contain an CR, wait 50 ms and then try again. If
|
|
||||||
we receive garbage again, propagate the error.
|
|
||||||
*/
|
|
||||||
// Check for CR
|
|
||||||
bool hasEtx = false;
|
|
||||||
for (ulong i = 0; i < sizeof(fullResponse); i++) {
|
|
||||||
if (fullResponse[i] == '\x0d') {
|
|
||||||
hasEtx = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasEtx) {
|
|
||||||
usleep(50000); // 50 ms
|
|
||||||
|
|
||||||
// Try again ...
|
|
||||||
status = pasynOctetSyncIO->writeRead(
|
|
||||||
lowLevelPortUser_, fullCommand.c_str(), fullCommand.length(),
|
|
||||||
fullResponse, MAXBUF_, comTimeout_, &nbytesOut, &nbytesIn,
|
|
||||||
&eomReason);
|
|
||||||
|
|
||||||
// Does this message contain an CR?
|
|
||||||
for (ulong i = 0; i < sizeof(fullResponse); i++) {
|
|
||||||
if (fullResponse[i] == '\x0d') {
|
|
||||||
hasEtx = true;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (i + 1 == maxTrials && status == asynError) {
|
||||||
|
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
|
"%s => line %d:\nFailed %d times to get the "
|
||||||
|
"correct response. Aborting read.\n",
|
||||||
|
__PRETTY_FUNCTION__, __LINE__, maxTrials);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!hasEtx) {
|
|
||||||
adjustForPrint(printableCommand, fullCommand.c_str(), MAXBUF_);
|
|
||||||
adjustForPrint(printableResponse, fullResponse, MAXBUF_);
|
|
||||||
// Failed for the second time => Give up and propagate the error.
|
|
||||||
asynPrint(lowLevelPortUser_, ASYN_TRACE_ERROR,
|
|
||||||
"%s => line %d:\nReceived garbage response '%s' for "
|
|
||||||
"command '%s' two times in a row.\n",
|
|
||||||
__PRETTY_FUNCTION__, __LINE__, printableResponse,
|
|
||||||
printableCommand);
|
|
||||||
status = asynError;
|
|
||||||
} else {
|
|
||||||
usleep(1000); // 1 ms
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
usleep(50000); // 1 ms
|
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
|
"%s => line %d:\nError %s while writing to the controller\n",
|
||||||
|
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MasterMACS needs a bit of time between messages, therefore thr program
|
||||||
|
// execution is paused after the communication happened.
|
||||||
|
// usleep(1500);
|
||||||
|
|
||||||
// Create custom error messages for different failure modes
|
// Create custom error messages for different failure modes
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case asynSuccess:
|
case asynSuccess:
|
||||||
|
|
||||||
/*
|
if (isRead) {
|
||||||
A response looks like this (note the spaces, they are part of the
|
|
||||||
message!):
|
|
||||||
- [ENQ][LSB][MSB][PDO1] 1 R 2=12.819[ACK][CR] (No error)
|
|
||||||
- [ENQ][LSB][MSB][PDO1] 1 R 2=12.819[NAK][CR] (Communication failed)
|
|
||||||
- [ENQ][LSB][MSB][PDO1] 1 S 10 [CAN][CR] (Driver tried to write with a
|
|
||||||
read-only command)
|
|
||||||
Read out the second-to-last char of the response and check if it is NAK
|
|
||||||
or CAN.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// We don't use strlen here since the C string terminator 0x00 occurs in
|
|
||||||
// the middle of the char array.
|
|
||||||
for (ulong i = 0; i < sizeof(fullResponse); i++) {
|
|
||||||
if (fullResponse[i] == '=') {
|
|
||||||
dataStartIndex = i + 1;
|
|
||||||
}
|
|
||||||
if (fullResponse[i] == '\x06') {
|
|
||||||
validIndex = i;
|
|
||||||
responseValid = true;
|
|
||||||
break;
|
|
||||||
} else if (fullResponse[i] == '\x15') {
|
|
||||||
// NAK
|
|
||||||
snprintf(drvMessageText, sizeof(drvMessageText),
|
|
||||||
"Communication failed.");
|
|
||||||
responseValid = false;
|
|
||||||
break;
|
|
||||||
} else if (fullResponse[i] == '\x18') {
|
|
||||||
// CAN
|
|
||||||
snprintf(drvMessageText, sizeof(drvMessageText),
|
|
||||||
"Tried to write with a read-only command. This is a "
|
|
||||||
"bug, please call the support.");
|
|
||||||
responseValid = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (responseValid) {
|
|
||||||
/*
|
/*
|
||||||
If a property has been read, we need just the part between the "="
|
If a property has been read, we need just the part between the
|
||||||
(0x3D) and the [ACK] (0x06). Therefore, we remove all non-needed
|
"=" (0x3D) and the [ACK] (0x06). Therefore, we remove all
|
||||||
parts after evaluating the second-to-last char before returning the
|
non-needed parts after evaluating the second-to-last char before
|
||||||
response.
|
returning the response.
|
||||||
*/
|
*/
|
||||||
for (int i = 0; i + dataStartIndex < validIndex; i++) {
|
for (int i = 0; i + valueStart < valueStop; i++) {
|
||||||
response[i] = fullResponse[i + dataStartIndex];
|
response[i] = fullResponse[i + valueStart];
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
status = asynError;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
break; // Communicate nothing
|
break;
|
||||||
case asynTimeout:
|
case asynTimeout:
|
||||||
snprintf(drvMessageText, sizeof(drvMessageText),
|
snprintf(drvMessageText, sizeof(drvMessageText),
|
||||||
"connection timeout for axis %d", axisNo);
|
"connection timeout for axis %d", axisNo);
|
||||||
@ -391,62 +329,28 @@ asynStatus masterMacsController::writeRead(int axisNo, const char *command,
|
|||||||
case asynDisabled:
|
case asynDisabled:
|
||||||
snprintf(drvMessageText, sizeof(drvMessageText), "axis is disabled");
|
snprintf(drvMessageText, sizeof(drvMessageText), "axis is disabled");
|
||||||
break;
|
break;
|
||||||
|
case asynError:
|
||||||
|
// Do nothing - error message drvMessageText has already been set.
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
snprintf(drvMessageText, sizeof(drvMessageText),
|
snprintf(drvMessageText, sizeof(drvMessageText),
|
||||||
"Communication failed (%s)", stringifyAsynStatus(status));
|
"Communication failed (%s)", stringifyAsynStatus(status));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ___________________________________________________________DEBUG
|
|
||||||
|
|
||||||
// adjustForPrint(printableCommand, fullCommand, MAXBUF_);
|
|
||||||
// adjustForPrint(printableResponse, fullResponse, MAXBUF_);
|
|
||||||
|
|
||||||
// asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
|
||||||
// "\n------------------c\n"); for (int i = 0; i < 12; ++i) {
|
|
||||||
// asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, "%x: %c\n",
|
|
||||||
// fullCommand[i], fullCommand[i]);
|
|
||||||
// }
|
|
||||||
// for (int i = 0; i < 12; ++i) {
|
|
||||||
// asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, "%x: %c\n",
|
|
||||||
// printableCommand[i], printableCommand[i]);
|
|
||||||
// }
|
|
||||||
// asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
|
||||||
// "\n=================\n"); for (int i = 0; i < 12; ++i) {
|
|
||||||
// asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, "%x: %c\n",
|
|
||||||
// fullResponse[i], fullResponse[i]);
|
|
||||||
// }
|
|
||||||
// for (int i = 0; i < 12; ++i) {
|
|
||||||
// asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, "%x: %c\n",
|
|
||||||
// printableResponse[i], printableResponse[i]);
|
|
||||||
// }
|
|
||||||
// asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
|
||||||
// "\n++++++++++++++++++++\n");
|
|
||||||
|
|
||||||
// asynPrint(lowLevelPortUser_, ASYN_TRACE_ERROR,
|
|
||||||
// "%s => line %d:\nResponse '%s' for command '%s'.\n",
|
|
||||||
// __PRETTY_FUNCTION__, __LINE__, printableResponse,
|
|
||||||
// printableCommand);
|
|
||||||
// ___________________________________________________________DEBUG
|
|
||||||
|
|
||||||
// Log the overall status (communication successfull or not)
|
// Log the overall status (communication successfull or not)
|
||||||
if (status == asynSuccess) {
|
if (status == asynSuccess) {
|
||||||
|
adjustForPrint(printableResponse, fullResponse, MAXBUF_);
|
||||||
asynPrint(lowLevelPortUser_, ASYN_TRACEIO_DRIVER,
|
asynPrint(lowLevelPortUser_, ASYN_TRACEIO_DRIVER,
|
||||||
"%s => line %d:\nReturn value: %s\n", __PRETTY_FUNCTION__,
|
"%s => line %d:\nReturn value: %s\n", __PRETTY_FUNCTION__,
|
||||||
__LINE__, response);
|
__LINE__, printableResponse);
|
||||||
pl_status = axis->setIntegerParam(this->motorStatusCommsError_, 0);
|
pl_status = axis->setIntegerParam(this->motorStatusCommsError_, 0);
|
||||||
} else {
|
} else {
|
||||||
adjustForPrint(printableCommand, fullCommand.c_str(), MAXBUF_);
|
|
||||||
asynPrint(
|
|
||||||
lowLevelPortUser_, ASYN_TRACE_ERROR,
|
|
||||||
"%s => line %d:\nCommunication failed for command '%s' (%s)\n",
|
|
||||||
__PRETTY_FUNCTION__, __LINE__, printableCommand,
|
|
||||||
stringifyAsynStatus(status));
|
|
||||||
|
|
||||||
// Check if the axis already is in an error communication mode. If it is
|
// Check if the axis already is in an error communication mode. If
|
||||||
// not, upstream the error. This is done to avoid "flooding" the user
|
// it is not, upstream the error. This is done to avoid "flooding"
|
||||||
// with different error messages if more than one error ocurred before
|
// the user with different error messages if more than one error
|
||||||
// an error-free communication
|
// ocurred before an error-free communication
|
||||||
pl_status =
|
pl_status =
|
||||||
getIntegerParam(axisNo, motorStatusProblem_, &motorStatusProblem);
|
getIntegerParam(axisNo, motorStatusProblem_, &motorStatusProblem);
|
||||||
if (pl_status != asynSuccess) {
|
if (pl_status != asynSuccess) {
|
||||||
@ -474,9 +378,96 @@ asynStatus masterMacsController::writeRead(int axisNo, const char *command,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
A response looks like this (note the spaces, they are part of the
|
||||||
|
message!):
|
||||||
|
- [ENQ][LSB][MSB][PDO1] 1 R 2=12.819[ACK][CR] (No error)
|
||||||
|
- [ENQ][LSB][MSB][PDO1] 1 R 2=12.819[NAK][CR] (Communication failed)
|
||||||
|
- [ENQ][LSB][MSB][PDO1] 1 S 10 [CAN][CR] (Driver tried to write with
|
||||||
|
a read-only command) Read out the second-to-last char of the
|
||||||
|
response and check if it is NAK or CAN.
|
||||||
|
*/
|
||||||
|
asynStatus masterMacsController::parseResponse(
|
||||||
|
const char *fullCommand, const char *fullResponse, char *drvMessageText,
|
||||||
|
int *valueStart, int *valueStop, int axisNo, int tcpCmd, bool isRead) {
|
||||||
|
|
||||||
|
bool responseValid = false;
|
||||||
|
int responseStart = 0;
|
||||||
|
char printableCommand[MAXBUF_] = {0};
|
||||||
|
char printableResponse[MAXBUF_] = {0};
|
||||||
|
|
||||||
|
// We don't use strlen here since the C string terminator 0x00
|
||||||
|
// occurs in the middle of the char array.
|
||||||
|
for (uint32_t i = 0; i < MAXBUF_; i++) {
|
||||||
|
if (fullResponse[i] == '\x19') {
|
||||||
|
responseStart = i;
|
||||||
|
} else if (fullResponse[i] == '=') {
|
||||||
|
*valueStart = i + 1;
|
||||||
|
} else if (fullResponse[i] == '\x06') {
|
||||||
|
*valueStop = i;
|
||||||
|
responseValid = true;
|
||||||
|
break;
|
||||||
|
} else if (fullResponse[i] == '\x15') {
|
||||||
|
// NAK
|
||||||
|
snprintf(drvMessageText, MAXBUF_, "Communication failed.");
|
||||||
|
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
|
"%s => line %d:\nCommunication failed\n",
|
||||||
|
__PRETTY_FUNCTION__, __LINE__);
|
||||||
|
break;
|
||||||
|
} else if (fullResponse[i] == '\x18') {
|
||||||
|
// CAN
|
||||||
|
snprintf(drvMessageText, MAXBUF_,
|
||||||
|
"Tried to write with a read-only command. This is a "
|
||||||
|
"bug, please call the support.");
|
||||||
|
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
|
"%s => line %d:\nTried to write with the read-only "
|
||||||
|
"command %s\n",
|
||||||
|
__PRETTY_FUNCTION__, __LINE__, printableCommand);
|
||||||
|
responseValid = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (responseValid) {
|
||||||
|
// Check if the response matches the expectations. Each response
|
||||||
|
// contains the string "axisNo R tcpCmd" (including the spaces)
|
||||||
|
char expectedResponseSubstring[MAXBUF_] = {0};
|
||||||
|
|
||||||
|
// The response does not contain a leading 0 if tcpCmd only has
|
||||||
|
// a single digit!
|
||||||
|
if (isRead) {
|
||||||
|
snprintf(expectedResponseSubstring, MAXBUF_ - 4, "%d R %d", axisNo,
|
||||||
|
tcpCmd);
|
||||||
|
} else {
|
||||||
|
snprintf(expectedResponseSubstring, MAXBUF_ - 4, "%d S %d", axisNo,
|
||||||
|
tcpCmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strstr(&fullResponse[responseStart], expectedResponseSubstring) ==
|
||||||
|
NULL) {
|
||||||
|
adjustForPrint(printableCommand, fullCommand, MAXBUF_);
|
||||||
|
adjustForPrint(printableResponse, fullResponse, MAXBUF_);
|
||||||
|
|
||||||
|
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
|
"%s => line %d:\nMismatched response %s to "
|
||||||
|
"command %s\n",
|
||||||
|
__PRETTY_FUNCTION__, __LINE__, printableResponse,
|
||||||
|
printableCommand);
|
||||||
|
|
||||||
|
snprintf(drvMessageText, MAXBUF_,
|
||||||
|
"Mismatched response %s to command %s. Please call the "
|
||||||
|
"support.",
|
||||||
|
printableResponse, printableCommand);
|
||||||
|
return asynError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return asynSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
asynStatus sinqController::readInt32(asynUser *pasynUser, epicsInt32 *value) {
|
asynStatus sinqController::readInt32(asynUser *pasynUser, epicsInt32 *value) {
|
||||||
// masterMacs can be disabled
|
// masterMacs can be disabled
|
||||||
if (pasynUser->reason == motorCanDisable_) {
|
if (pasynUser->reason == motorCanDisable_) {
|
||||||
@ -508,8 +499,8 @@ asynStatus masterMacsCreateController(const char *portName,
|
|||||||
https://github.com/epics-modules/motor/blob/master/motorApp/MotorSrc/asynMotorController.cpp
|
https://github.com/epics-modules/motor/blob/master/motorApp/MotorSrc/asynMotorController.cpp
|
||||||
https://github.com/epics-modules/asyn/blob/master/asyn/asynPortDriver/asynPortDriver.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
|
The created object is registered in EPICS in its constructor and can
|
||||||
be "leaked" here.
|
safely be "leaked" here.
|
||||||
*/
|
*/
|
||||||
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
|
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
|
||||||
#pragma GCC diagnostic ignored "-Wunused-variable"
|
#pragma GCC diagnostic ignored "-Wunused-variable"
|
||||||
@ -574,8 +565,8 @@ asynStatus masterMacsCreateAxis(const char *portName, int axis) {
|
|||||||
https://github.com/epics-modules/motor/blob/master/motorApp/MotorSrc/asynMotorController.cpp
|
https://github.com/epics-modules/motor/blob/master/motorApp/MotorSrc/asynMotorController.cpp
|
||||||
https://github.com/epics-modules/asyn/blob/master/asyn/asynPortDriver/asynPortDriver.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
|
The created object is registered in EPICS in its constructor and can
|
||||||
be "leaked" here.
|
safely be "leaked" here.
|
||||||
*/
|
*/
|
||||||
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
|
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
|
||||||
#pragma GCC diagnostic ignored "-Wunused-variable"
|
#pragma GCC diagnostic ignored "-Wunused-variable"
|
||||||
@ -637,8 +628,8 @@ static void configMasterMacsCreateAxisCallFunc(const iocshArgBuf *args) {
|
|||||||
masterMacsCreateAxis(args[0].sval, args[1].ival);
|
masterMacsCreateAxis(args[0].sval, args[1].ival);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function is made known to EPICS in masterMacs.dbd and is called by EPICS
|
// This function is made known to EPICS in masterMacs.dbd and is called by
|
||||||
// in order to register both functions in the IOC shell
|
// EPICS in order to register both functions in the IOC shell
|
||||||
static void masterMacsRegister(void) {
|
static void masterMacsRegister(void) {
|
||||||
iocshRegister(&configMasterMacsCreateController,
|
iocshRegister(&configMasterMacsCreateController,
|
||||||
configMasterMacsCreateControllerCallFunc);
|
configMasterMacsCreateControllerCallFunc);
|
||||||
|
@ -53,23 +53,66 @@ class masterMacsController : public sinqController {
|
|||||||
asynUser *lowLevelPortUser_;
|
asynUser *lowLevelPortUser_;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Send a command to the hardware and receive a response
|
* @brief Send a command to the hardware (S mode)
|
||||||
*
|
*
|
||||||
* @param axisNo Axis to which the command should be send
|
* @param axisNo Axis to which the command should be send
|
||||||
* @param command Command for the hardware
|
* @param tcpCmd TCP command key
|
||||||
* @param response Buffer for the response. This buffer is
|
* @param payload Value send to MasterMACS.
|
||||||
* 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
|
* @return asynStatus
|
||||||
*/
|
*/
|
||||||
asynStatus writeRead(int axisNo, const char *command, char *response,
|
asynStatus write(int axisNo, int tcpCmd, const char *payload);
|
||||||
bool expectResponse);
|
|
||||||
|
/**
|
||||||
|
* @brief Send a command to the hardware and receive a response (R mode)
|
||||||
|
*
|
||||||
|
* @param axisNo Axis to which the command should be send
|
||||||
|
* @param tcpCmd TCP command key
|
||||||
|
* @param response Buffer for the response. This buffer is
|
||||||
|
* expected to have the size MAXBUF_.
|
||||||
|
* @return asynStatus
|
||||||
|
*/
|
||||||
|
asynStatus read(int axisNo, int tcpCmd, char *response);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Send a command to the hardware (R or S mode) and receive a
|
||||||
|
* response in the former case.
|
||||||
|
*
|
||||||
|
* This function is the internal implementation of the write and read
|
||||||
|
* methods.
|
||||||
|
*
|
||||||
|
* @param axisNo Axis to which the command should be send
|
||||||
|
* @param tcpCmd TCP command key
|
||||||
|
* @param payload Value send to MasterMACS.
|
||||||
|
* @param response Buffer for the response. This buffer is
|
||||||
|
* expected to have the size MAXBUF_.
|
||||||
|
* @return asynStatus
|
||||||
|
*/
|
||||||
|
asynStatus writeRead(int axisNo, int tcpCmd, const char *payload,
|
||||||
|
char *response);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Parse "fullResponse" received upon sending "fullCommand".
|
||||||
|
*
|
||||||
|
* Returns asynSuccess if the response is valid and asynError otherwise.
|
||||||
|
* This is an internal function used in writeRead.
|
||||||
|
*
|
||||||
|
* @param fullCommand Command which was send to the controller
|
||||||
|
* @param fullResponse Response which was received
|
||||||
|
* @param drvMessageText Buffer for messages send to the user
|
||||||
|
* @param valueStart If a payload was included in fullResponse,
|
||||||
|
* return the index where it starts.
|
||||||
|
* @param valueStop If a payload was included in fullResponse,
|
||||||
|
* return the index where it stops.
|
||||||
|
* @param axisNo Axis to which the command should be send
|
||||||
|
* @param tcpCmd TCP command key
|
||||||
|
* @param isRead true, if a payload is expect as part of the
|
||||||
|
* answer
|
||||||
|
* @return asynStatus
|
||||||
|
*/
|
||||||
|
asynStatus parseResponse(const char *fullCommand, const char *fullResponse,
|
||||||
|
char *drvMessageText, int *valueStart,
|
||||||
|
int *valueStop, int axisNo, int tcpCmd,
|
||||||
|
bool isRead);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Save cast of the given asynAxis pointer to a masterMacsAxis
|
* @brief Save cast of the given asynAxis pointer to a masterMacsAxis
|
||||||
|
Reference in New Issue
Block a user