Initial commit for EL734 driver based on sinqMotor
This commit is contained in:
Vendored
BIN
Binary file not shown.
Vendored
+33
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"env": {
|
||||
"myIncludePath": [
|
||||
"${workspaceFolder}",
|
||||
"/home/dev/epics_modules",
|
||||
"${env:EPICS_BASE}"
|
||||
]
|
||||
},
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Linux",
|
||||
"includePath": [
|
||||
"${workspaceFolder}/**",
|
||||
"/home/dev/epics_modules/**",
|
||||
"/home/dev/sinqMotor/**",
|
||||
"${env:EPICS_BASE}/**"
|
||||
],
|
||||
"defines": [],
|
||||
"cStandard": "c17",
|
||||
"cppStandard": "gnu++14",
|
||||
"intelliSenseMode": "linux-gcc-x64",
|
||||
"compilerPath": "/usr/bin/gcc",
|
||||
"browse": {
|
||||
"path": [
|
||||
"${workspaceFolder}"
|
||||
],
|
||||
"limitSymbolsToIncludedHeaders": true,
|
||||
"databaseFilename": "${workspaceFolder}/.vscode/browse.vc.db"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version": 4
|
||||
}
|
||||
Vendored
+49
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"C_Cpp.errorSquiggles": "enabled",
|
||||
"files.associations": {
|
||||
"atomic": "cpp",
|
||||
"*.tcc": "cpp",
|
||||
"deque": "cpp",
|
||||
"string": "cpp",
|
||||
"unordered_map": "cpp",
|
||||
"vector": "cpp",
|
||||
"iterator": "cpp",
|
||||
"memory_resource": "cpp",
|
||||
"optional": "cpp",
|
||||
"string_view": "cpp",
|
||||
"fstream": "cpp",
|
||||
"istream": "cpp",
|
||||
"ostream": "cpp",
|
||||
"sstream": "cpp",
|
||||
"streambuf": "cpp",
|
||||
"system_error": "cpp",
|
||||
"functional": "cpp",
|
||||
"tuple": "cpp",
|
||||
"limits": "cpp",
|
||||
"type_traits": "cpp",
|
||||
"array": "cpp",
|
||||
"cctype": "cpp",
|
||||
"clocale": "cpp",
|
||||
"cmath": "cpp",
|
||||
"cstdarg": "cpp",
|
||||
"cstddef": "cpp",
|
||||
"cstdint": "cpp",
|
||||
"cstdio": "cpp",
|
||||
"cstdlib": "cpp",
|
||||
"cwchar": "cpp",
|
||||
"cwctype": "cpp",
|
||||
"exception": "cpp",
|
||||
"algorithm": "cpp",
|
||||
"memory": "cpp",
|
||||
"numeric": "cpp",
|
||||
"random": "cpp",
|
||||
"utility": "cpp",
|
||||
"initializer_list": "cpp",
|
||||
"iosfwd": "cpp",
|
||||
"iostream": "cpp",
|
||||
"new": "cpp",
|
||||
"stdexcept": "cpp",
|
||||
"typeinfo": "cpp"
|
||||
},
|
||||
"editor.formatOnSave": true
|
||||
}
|
||||
Vendored
@@ -0,0 +1,2 @@
|
||||
registrar(el734ControllerRegister)
|
||||
registrar(el734AxisRegister)
|
||||
@@ -0,0 +1,792 @@
|
||||
#include "el734Axis.h"
|
||||
#include "asynOctetSyncIO.h"
|
||||
#include "el734Controller.h"
|
||||
#include "epicsExport.h"
|
||||
#include "iocsh.h"
|
||||
#include <bitset>
|
||||
#include <cmath>
|
||||
#include <errlog.h>
|
||||
#include <initHooks.h>
|
||||
#include <limits>
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
|
||||
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) {
|
||||
|
||||
// We check before that the capacity of fullMessage is sufficient
|
||||
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';
|
||||
|
||||
// We check before that the capacity of fullMessage is sufficient
|
||||
strcat(fullMessage, toBeAppended);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct el734AxisImpl {
|
||||
/*
|
||||
The functions below read the specified status bit from the axisStatus (see
|
||||
el734AxisImpl redefinition in el734Axis.cpp) bitset. Since a bit
|
||||
can either be 0 or 1, the return value is given as a boolean. See
|
||||
el734_manual.pdf, p. 69.
|
||||
*/
|
||||
bool busy;
|
||||
bool positionReady;
|
||||
bool referencePositionReady;
|
||||
bool stopOnSwitchOrCommand;
|
||||
bool stopOnLowerSwitch;
|
||||
bool stopOnUpperSwitch;
|
||||
bool stopOnHalt;
|
||||
bool runFault;
|
||||
bool positionFault;
|
||||
bool positionFailure;
|
||||
bool referenceFailure;
|
||||
bool airCushionFailure;
|
||||
bool notFreeFromSwitch;
|
||||
};
|
||||
|
||||
/*
|
||||
Contains all instances of el734Axis which have been created and is used in
|
||||
the initialization hook function.
|
||||
*/
|
||||
static std::vector<el734Axis *> 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<el734Axis *>::iterator itA = axes.begin();
|
||||
itA != axes.end(); ++itA) {
|
||||
el734Axis *axis = *itA;
|
||||
axis->init();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
el734Axis::el734Axis(el734Controller *pC, int axisNo, bool initialize)
|
||||
: sinqAxis(pC, axisNo), pC_(pC) {
|
||||
|
||||
asynStatus status = asynSuccess;
|
||||
|
||||
if (initialize) {
|
||||
// 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);
|
||||
}
|
||||
|
||||
pEl734A_ = std::make_unique<el734AxisImpl>((el734AxisImpl){});
|
||||
|
||||
// Even though this happens already in sinqAxis, a default value for
|
||||
// motorMessageText is set here again, because apparently the sinqAxis
|
||||
// constructor is not run before the string is accessed?
|
||||
status = setStringParam(pC_->motorMessageText(), "");
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(pC_->pasynUser(), 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);
|
||||
}
|
||||
|
||||
// el734 motors cannot be disabled
|
||||
status = pC_->setIntegerParam(axisNo_, pC_->motorCanDisable(), 0);
|
||||
if (status != asynSuccess) {
|
||||
pC_->paramLibAccessFailed(status, "motorCanDisable", axisNo_,
|
||||
__PRETTY_FUNCTION__, __LINE__);
|
||||
}
|
||||
|
||||
// Default values for the watchdog timeout mechanism
|
||||
setOffsetMovTimeout(30.0); // seconds
|
||||
setScaleMovTimeout(2.0);
|
||||
}
|
||||
|
||||
el734Axis::~el734Axis(void) {
|
||||
// Since the controller memory is managed somewhere else, we don't need to
|
||||
// clean up the pointer pC here.
|
||||
}
|
||||
|
||||
asynStatus el734Axis::init() {
|
||||
|
||||
// Local variable declaration
|
||||
asynStatus status = asynSuccess;
|
||||
char command[pC_->MAXBUF_] = {0};
|
||||
char response[pC_->MAXBUF_] = {0};
|
||||
double motorRecResolution = 0.0;
|
||||
double motorPos = 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) {
|
||||
status = pC_->getDoubleParam(axisNo_, pC_->motorRecResolution(),
|
||||
&motorRecResolution);
|
||||
if (status == asynParamUndefined) {
|
||||
if (now + maxInitTime < time(NULL)) {
|
||||
asynPrint(pC_->pasynUser(), 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 (status == asynSuccess) {
|
||||
break;
|
||||
} else if (status != asynSuccess) {
|
||||
return pC_->paramLibAccessFailed(status, "motorRecResolution_",
|
||||
axisNo_, __PRETTY_FUNCTION__,
|
||||
__LINE__);
|
||||
}
|
||||
}
|
||||
|
||||
// Read motor position
|
||||
snprintf(command, sizeof(command), "U %d", axisNo_);
|
||||
status = pC_->writeRead(axisNo_, command, response);
|
||||
if (status != asynSuccess) {
|
||||
return status;
|
||||
}
|
||||
sscanf(response, "%lf", &motorPos);
|
||||
|
||||
// Store these values in the parameter library
|
||||
status = setMotorPosition(motorPos);
|
||||
if (status != asynSuccess) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// Initial motor status is idle
|
||||
setAxisParamChecked(this, motorStatusDone, 1);
|
||||
|
||||
// Update the parameter library immediately
|
||||
status = callParamCallbacks();
|
||||
if (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_->pasynUser(), ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\", axis %d => %s, line %d\ncallParamCallbacks "
|
||||
"failed with %s.\n",
|
||||
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
|
||||
pC_->stringifyAsynStatus(status));
|
||||
return status;
|
||||
}
|
||||
|
||||
// Motor cannot be disabled / enabled -> set the PV to enabled by default
|
||||
setAxisParamChecked(this, motorEnableRBV, true);
|
||||
|
||||
// Motor is always connected
|
||||
setAxisParamChecked(this, motorConnected, true);
|
||||
|
||||
// Motor cannot be disabled
|
||||
setAxisParamChecked(this, motorCanDisable, false);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
// Perform the actual poll
|
||||
asynStatus el734Axis::doPoll(bool *moving) {
|
||||
|
||||
// Return value for the poll
|
||||
asynStatus errorStatus = asynSuccess;
|
||||
|
||||
// Status of read-write-operations of ASCII commands to the controller
|
||||
asynStatus status = asynSuccess;
|
||||
|
||||
char command[pC_->MAXBUF_] = {0};
|
||||
char response[pC_->MAXBUF_] = {0};
|
||||
|
||||
int direction = 0;
|
||||
double currentPosition = 0.0;
|
||||
double previousPosition = 0.0;
|
||||
double motorRecResolution = 0.0;
|
||||
double highLimit = 0.0;
|
||||
double lowLimit = 0.0;
|
||||
double limitsOffset = 0.0;
|
||||
|
||||
// =========================================================================
|
||||
|
||||
getAxisParamChecked(this, motorRecResolution, &motorRecResolution);
|
||||
|
||||
// Read the previous motor position
|
||||
status = motorPosition(&previousPosition);
|
||||
if (status != asynSuccess) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// Read the axis status
|
||||
status = readAxisStatus();
|
||||
if (status != asynSuccess) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// Read the axis position
|
||||
snprintf(command, sizeof(command), "U %d", axisNo_);
|
||||
status = pC_->writeRead(axisNo_, command, response);
|
||||
if (status != asynSuccess) {
|
||||
return status;
|
||||
}
|
||||
sscanf(response, "%lf", ¤tPosition);
|
||||
|
||||
// Read the axis limits
|
||||
snprintf(command, sizeof(command), "H %d", axisNo_);
|
||||
status = pC_->writeRead(axisNo_, command, response);
|
||||
if (status != asynSuccess) {
|
||||
return status;
|
||||
}
|
||||
sscanf(response, "%lf %lf", &lowLimit, &highLimit);
|
||||
|
||||
/*
|
||||
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 should 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 limitsOffset on both sides.
|
||||
*/
|
||||
getAxisParamChecked(this, motorLimitsOffset, &limitsOffset);
|
||||
highLimit = highLimit - limitsOffset;
|
||||
lowLimit = lowLimit + limitsOffset;
|
||||
|
||||
// Interpret the status
|
||||
*moving = pEl734A_->busy;
|
||||
|
||||
// Interpret the errors
|
||||
// This buffer must be initialized to zero because we build the
|
||||
// error message by appending strings.
|
||||
char errorMessage[pC_->MAXBUF_] = {0};
|
||||
char shellMessage[pC_->MAXBUF_] = {0};
|
||||
|
||||
// Concatenate all other errors
|
||||
if (pEl734A_->stopOnLowerSwitch) {
|
||||
appendErrorMessage(shellMessage, sizeof(shellMessage),
|
||||
"Hit low limit switch.");
|
||||
appendErrorMessage(errorMessage, sizeof(errorMessage),
|
||||
"Hit low limit switch.");
|
||||
errorStatus = asynError;
|
||||
}
|
||||
|
||||
if (pEl734A_->stopOnUpperSwitch) {
|
||||
appendErrorMessage(shellMessage, sizeof(shellMessage),
|
||||
"Hit high limit switch.");
|
||||
appendErrorMessage(errorMessage, sizeof(errorMessage),
|
||||
"Hit high limit switch.");
|
||||
errorStatus = asynError;
|
||||
}
|
||||
|
||||
if (pEl734A_->runFault) {
|
||||
appendErrorMessage(shellMessage, sizeof(shellMessage), "Run fault.");
|
||||
appendErrorMessage(errorMessage, sizeof(errorMessage),
|
||||
"Run fault. Please call the support");
|
||||
errorStatus = asynError;
|
||||
}
|
||||
|
||||
if (pEl734A_->positionFault) {
|
||||
appendErrorMessage(shellMessage, sizeof(shellMessage),
|
||||
"Position fault.");
|
||||
appendErrorMessage(errorMessage, sizeof(errorMessage),
|
||||
"Position fault. Please call the support");
|
||||
errorStatus = asynError;
|
||||
}
|
||||
|
||||
if (pEl734A_->positionFailure) {
|
||||
appendErrorMessage(shellMessage, sizeof(shellMessage),
|
||||
"Position failure.");
|
||||
appendErrorMessage(errorMessage, sizeof(errorMessage),
|
||||
"Position failure. Please call the support");
|
||||
errorStatus = asynError;
|
||||
}
|
||||
|
||||
if (pEl734A_->referenceFailure) {
|
||||
appendErrorMessage(shellMessage, sizeof(shellMessage),
|
||||
"Reference failure.");
|
||||
appendErrorMessage(errorMessage, sizeof(errorMessage),
|
||||
"Reference failure. Please call the support");
|
||||
errorStatus = asynError;
|
||||
}
|
||||
|
||||
if (pEl734A_->airCushionFailure) {
|
||||
appendErrorMessage(shellMessage, sizeof(shellMessage),
|
||||
"Air cushion failure.");
|
||||
appendErrorMessage(errorMessage, sizeof(errorMessage),
|
||||
"Air cushion failure. Please call the support");
|
||||
errorStatus = asynError;
|
||||
}
|
||||
|
||||
if (strlen(shellMessage) > 0) {
|
||||
if (pC_->getMsgPrintControl().shouldBePrinted(
|
||||
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, true,
|
||||
pC_->pasynUser())) {
|
||||
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\", axis %d => %s, line "
|
||||
"%d\n%s%s\n",
|
||||
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
|
||||
shellMessage, pC_->getMsgPrintControl().getSuffix());
|
||||
}
|
||||
}
|
||||
|
||||
setAxisParamChecked(this, motorMessageText, errorMessage);
|
||||
|
||||
// Update the parameter library
|
||||
if (*moving == false) {
|
||||
setAxisParamChecked(this, motorMoveToHome, false);
|
||||
}
|
||||
|
||||
setAxisParamChecked(this, motorStatusMoving, *moving);
|
||||
setAxisParamChecked(this, motorStatusDone, !(*moving));
|
||||
setAxisParamChecked(this, motorStatusDirection, direction);
|
||||
|
||||
int limFromHardware = 0;
|
||||
getAxisParamChecked(this, limFromHardware, &limFromHardware);
|
||||
|
||||
if (limFromHardware != 0) {
|
||||
setAxisParamChecked(this, motorHighLimitFromDriver, highLimit);
|
||||
setAxisParamChecked(this, motorLowLimitFromDriver, lowLimit);
|
||||
}
|
||||
|
||||
status = setMotorPosition(currentPosition);
|
||||
if (status != asynSuccess) {
|
||||
return status;
|
||||
}
|
||||
return errorStatus;
|
||||
}
|
||||
|
||||
asynStatus el734Axis::doMove(double position, int relative, double minVelocity,
|
||||
double maxVelocity, double acceleration) {
|
||||
|
||||
// Status of read-write-operations of ASCII commands to the controller
|
||||
asynStatus status = asynSuccess;
|
||||
|
||||
char command[pC_->MAXBUF_] = {0};
|
||||
char response[pC_->MAXBUF_] = {0};
|
||||
double motorTargetPosition = 0.0;
|
||||
double motorRecResolution = 0.0;
|
||||
double motorVelocity = 0.0;
|
||||
int motorCanSetSpeed = 0;
|
||||
|
||||
// =========================================================================
|
||||
|
||||
// Suppress unused variable warnings
|
||||
(void)relative;
|
||||
(void)minVelocity;
|
||||
(void)acceleration;
|
||||
|
||||
getAxisParamChecked(this, motorRecResolution, &motorRecResolution);
|
||||
|
||||
// Convert from EPICS to user / motor units
|
||||
motorTargetPosition = position * motorRecResolution;
|
||||
motorVelocity = maxVelocity * motorRecResolution;
|
||||
|
||||
asynPrint(pC_->pasynUser(), ASYN_TRACE_FLOW,
|
||||
"Controller \"%s\", axis %d => %s, line %d\nStart of axis to "
|
||||
"position %lf.\n",
|
||||
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
|
||||
motorTargetPosition);
|
||||
|
||||
// Check if the speed is allowed to be changed
|
||||
getAxisParamChecked(this, motorCanSetSpeed, &motorCanSetSpeed);
|
||||
|
||||
if (motorCanSetSpeed != 0) {
|
||||
// EL734 only works with integer values, hence the float value is
|
||||
// truncated into an integer.
|
||||
snprintf(command, sizeof(command), "J %d %d", axisNo_,
|
||||
(int)motorVelocity);
|
||||
status = pC_->writeRead(axisNo_, command, response);
|
||||
if (status != asynSuccess) {
|
||||
|
||||
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\", axis %d => %s, line %d\nSetting "
|
||||
"target speed %lf failed.\n",
|
||||
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
|
||||
motorVelocity);
|
||||
setAxisParamChecked(this, motorStatusProblem, true);
|
||||
return status;
|
||||
}
|
||||
|
||||
asynPrint(pC_->pasynUser(), 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. The motor movements starts immediately.
|
||||
snprintf(command, sizeof(command), "P %d %lf", axisNo_,
|
||||
motorTargetPosition);
|
||||
status = pC_->writeRead(axisNo_, command, response);
|
||||
if (status != asynSuccess) {
|
||||
|
||||
asynPrint(
|
||||
pC_->pasynUser(), ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\", axis %d => %s, line %d\nStarting movement to "
|
||||
"target position %lf failed.\n",
|
||||
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
|
||||
motorTargetPosition);
|
||||
setAxisParamChecked(this, motorStatusProblem, true);
|
||||
return status;
|
||||
}
|
||||
|
||||
// Start monitoring the movement time.
|
||||
if (startMovTimeoutWatchdog() != asynSuccess) {
|
||||
return asynError;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
asynStatus el734Axis::stop(double acceleration) {
|
||||
|
||||
asynStatus status = asynSuccess;
|
||||
|
||||
char command[pC_->MAXBUF_] = {0};
|
||||
char response[pC_->MAXBUF_] = {0};
|
||||
|
||||
// =========================================================================
|
||||
|
||||
// Suppress unused variable warnings
|
||||
(void)acceleration;
|
||||
|
||||
snprintf(command, sizeof(command), "S %d", axisNo_);
|
||||
status = pC_->writeRead(axisNo_, command, response);
|
||||
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(
|
||||
pC_->pasynUser(), ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\", axis %d => %s, line %d\nStopping the movement "
|
||||
"failed.\n",
|
||||
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__);
|
||||
setAxisParamChecked(this, motorStatusProblem, true);
|
||||
}
|
||||
|
||||
/*
|
||||
Stopping the motor results in a movement and further move commands have to
|
||||
wait until the stopping movement is done. Therefore, we need to wait until
|
||||
the poller "sees" the changed state (otherwise, we risk issuing move
|
||||
commands while the motor is stopping). To ensure that at least one poll is
|
||||
done, this thread (which also runs move commands) is paused for twice the
|
||||
idle poll period.
|
||||
*/
|
||||
unsigned int idlePollMicros =
|
||||
(unsigned int)ceil(pC_->idlePollPeriod() * 1e6);
|
||||
usleep(2 * idlePollMicros);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
asynStatus el734Axis::setPosition(double position) {
|
||||
asynStatus status = asynSuccess;
|
||||
|
||||
char command[pC_->MAXBUF_] = {0};
|
||||
char response[pC_->MAXBUF_] = {0};
|
||||
|
||||
// Only continue if the motor doesn't have an encoder
|
||||
getAxisParamChecked(this, encoderType, &response);
|
||||
if (strcmp(response, NoEncoder) == 0) {
|
||||
double motorRecResolution = 0.0;
|
||||
getAxisParamChecked(this, motorRecResolution, &motorRecResolution);
|
||||
snprintf(command, sizeof(command), "U %d %lf", axisNo_,
|
||||
position * motorRecResolution);
|
||||
status = pC_->writeRead(axisNo_, command, response);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
asynStatus el734Axis::doReset() {
|
||||
|
||||
// Status of read-write-operations of ASCII commands to the controller
|
||||
asynStatus status = asynSuccess;
|
||||
|
||||
char response[pC_->MAXBUF_] = {0};
|
||||
|
||||
// =========================================================================
|
||||
|
||||
// Reset the entire controller
|
||||
status = pC_->writeRead(axisNo_, "SN CLR", response);
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\", axis %d => %s, line %d\nResetting the "
|
||||
"error failed\n",
|
||||
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__);
|
||||
setAxisParamChecked(this, motorStatusProblem, true);
|
||||
return status;
|
||||
}
|
||||
|
||||
// Switch the controller from local to remote mode
|
||||
status = pC_->writeRead(axisNo_, "RMT 1", response);
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\", axis %d => %s, line %d\nResetting the "
|
||||
"error failed\n",
|
||||
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__);
|
||||
setAxisParamChecked(this, motorStatusProblem, true);
|
||||
return status;
|
||||
}
|
||||
status = pC_->writeRead(axisNo_, "ECHO 0", response);
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\", axis %d => %s, line %d\nResetting the "
|
||||
"error failed\n",
|
||||
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__);
|
||||
setAxisParamChecked(this, motorStatusProblem, true);
|
||||
return status;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
asynStatus el734Axis::doHome(double min_velocity, double max_velocity,
|
||||
double acceleration, int forwards) {
|
||||
|
||||
// Status of read-write-operations of ASCII commands to the controller
|
||||
asynStatus status = asynSuccess;
|
||||
|
||||
char command[pC_->MAXBUF_] = {0};
|
||||
char response[pC_->MAXBUF_] = {0};
|
||||
|
||||
// =========================================================================
|
||||
|
||||
// Suppress unused variable warnings
|
||||
(void)min_velocity;
|
||||
(void)max_velocity;
|
||||
(void)acceleration;
|
||||
(void)forwards;
|
||||
|
||||
getAxisParamChecked(this, encoderType, &response);
|
||||
|
||||
// Only send the home command if the axis has an incremental encoder
|
||||
if (strcmp(response, IncrementalEncoder) == 0) {
|
||||
|
||||
snprintf(command, sizeof(command), "R %d", axisNo_);
|
||||
status = pC_->writeRead(axisNo_, command, response);
|
||||
if (status != asynSuccess) {
|
||||
return status;
|
||||
}
|
||||
|
||||
setAxisParamChecked(this, motorMoveToHome, true);
|
||||
return callParamCallbacks();
|
||||
}
|
||||
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
/*
|
||||
Read the encoder type and update the parameter library accordingly
|
||||
*/
|
||||
asynStatus el734Axis::readEncoderType() {
|
||||
|
||||
// Status of read-write-operations of ASCII commands to the controller
|
||||
asynStatus status = asynSuccess;
|
||||
|
||||
char command[pC_->MAXBUF_] = {0};
|
||||
char response[pC_->MAXBUF_] = {0};
|
||||
int nvals = 0;
|
||||
int encoder_id = 0;
|
||||
int encoder_type = 0;
|
||||
|
||||
// =========================================================================
|
||||
|
||||
// Read the encoder type.
|
||||
snprintf(command, sizeof(command), "EC %d", axisNo_);
|
||||
status = pC_->writeRead(axisNo_, command, response);
|
||||
if (status != asynSuccess) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// Two integers are reported: THe first one is the encoder type and the
|
||||
// second one the encoder id
|
||||
nvals = sscanf(response, "%d %d", &encoder_id, &encoder_type);
|
||||
if (nvals != 2) {
|
||||
return pC_->couldNotParseResponse(command, response, axisNo_,
|
||||
__PRETTY_FUNCTION__, __LINE__);
|
||||
}
|
||||
|
||||
// According to el734_manual, value 0 is no encoder, 1 is incremental
|
||||
// encoder and 2, 3 and 4 are absolute encoders (p. 62).
|
||||
switch (encoder_type) {
|
||||
case 0:
|
||||
setAxisParamChecked(this, encoderType, NoEncoder);
|
||||
break;
|
||||
case 1:
|
||||
setAxisParamChecked(this, encoderType, IncrementalEncoder);
|
||||
break;
|
||||
case 2:
|
||||
setAxisParamChecked(this, encoderType, AbsoluteEncoder);
|
||||
break;
|
||||
case 3:
|
||||
setAxisParamChecked(this, encoderType, AbsoluteEncoder);
|
||||
break;
|
||||
case 4:
|
||||
setAxisParamChecked(this, encoderType, AbsoluteEncoder);
|
||||
break;
|
||||
}
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
asynStatus el734Axis::readAxisStatus() {
|
||||
asynStatus status = asynSuccess;
|
||||
char command[pC_->MAXBUF_] = {0};
|
||||
char response[pC_->MAXBUF_] = {0};
|
||||
|
||||
// =========================================================================
|
||||
|
||||
snprintf(command, sizeof(command), "MSR %d", axisNo_);
|
||||
status = pC_->writeRead(axisNo_, command, response);
|
||||
if (status == asynSuccess) {
|
||||
|
||||
int axisStatus = 0;
|
||||
int nvals = sscanf(response, "%x", &axisStatus);
|
||||
if (nvals != 1) {
|
||||
return pC_->couldNotParseResponse(command, response, axisNo_,
|
||||
__PRETTY_FUNCTION__, __LINE__);
|
||||
}
|
||||
|
||||
// Read out the indidividual values
|
||||
pEl734A_->busy = axisStatus & 0x1;
|
||||
pEl734A_->positionReady = axisStatus & 0x2;
|
||||
pEl734A_->referencePositionReady = axisStatus & 0x4;
|
||||
pEl734A_->stopOnSwitchOrCommand = axisStatus & 0x8;
|
||||
pEl734A_->stopOnLowerSwitch = axisStatus & 0x10;
|
||||
pEl734A_->stopOnUpperSwitch = axisStatus & 0x20;
|
||||
pEl734A_->stopOnHalt = axisStatus & 0x40;
|
||||
pEl734A_->runFault = axisStatus & 0x80;
|
||||
pEl734A_->positionFault = axisStatus & 0x200;
|
||||
pEl734A_->positionFailure = axisStatus & 0x400;
|
||||
pEl734A_->referenceFailure = axisStatus & 0x800;
|
||||
pEl734A_->airCushionFailure = axisStatus & 0x1000;
|
||||
pEl734A_->notFreeFromSwitch = axisStatus & 0x2000;
|
||||
}
|
||||
|
||||
return 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 el734Axis
|
||||
constructor documentation. The controller is read from the portName.
|
||||
*/
|
||||
asynStatus el734CreateAxis(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
|
||||
https://stackoverflow.com/questions/70906749/is-there-a-safe-way-to-cast-void-to-class-pointer-in-c
|
||||
*/
|
||||
void *ptr = findAsynPortDriver(portName);
|
||||
if (ptr == nullptr) {
|
||||
/*
|
||||
We can't use asynPrint here since this macro would require us
|
||||
to get an asynUser from a pointer to an asynPortDriver.
|
||||
However, the given pointer is a nullptr and therefore doesn't
|
||||
have an asynUser! 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 not found.",
|
||||
portName, __PRETTY_FUNCTION__, __LINE__);
|
||||
return asynError;
|
||||
}
|
||||
// Unsafe cast of the pointer to an asynPortDriver
|
||||
asynPortDriver *apd = (asynPortDriver *)(ptr);
|
||||
|
||||
// Safe downcast
|
||||
el734Controller *pC = dynamic_cast<el734Controller *>(apd);
|
||||
if (pC == nullptr) {
|
||||
errlogPrintf("Controller \"%s\" => %s, line %d\nController "
|
||||
"is not a el734Controller.",
|
||||
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.
|
||||
https://github.com/epics-modules/motor/blob/master/motorApp/MotorSrc/asynMotorController.cpp
|
||||
https://github.com/epics-modules/asyn/blob/master/asyn/asynPortDriver/asynPortDriver.cpp
|
||||
|
||||
The created object is registered in EPICS in its constructor and can safely
|
||||
be "leaked" here.
|
||||
*/
|
||||
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
|
||||
#pragma GCC diagnostic ignored "-Wunused-variable"
|
||||
el734Axis *pAxis = new el734Axis(pC, axis);
|
||||
|
||||
// Allow manipulation of the controller again
|
||||
pC->unlock();
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
/*
|
||||
Same procedure as for the CreateController function, but for the axis
|
||||
itself.
|
||||
*/
|
||||
static const iocshArg CreateAxisArg0 = {"Controller name (e.g. mcu1)",
|
||||
iocshArgString};
|
||||
static const iocshArg CreateAxisArg1 = {"Axis number", iocshArgInt};
|
||||
static const iocshArg *const CreateAxisArgs[] = {&CreateAxisArg0,
|
||||
&CreateAxisArg1};
|
||||
static const iocshFuncDef configEl734CreateAxis = {
|
||||
"el734Axis", 2, CreateAxisArgs,
|
||||
"Create an instance of a el734Axis axis. The first argument is the "
|
||||
"controller this axis should be attached to, the second argument is the "
|
||||
"axis number."};
|
||||
static void configEl734CreateAxisCallFunc(const iocshArgBuf *args) {
|
||||
el734CreateAxis(args[0].sval, args[1].ival);
|
||||
}
|
||||
|
||||
// This function is made known to EPICS in el734.dbd and is called by EPICS
|
||||
// in order to register both functions in the IOC shell
|
||||
static void el734AxisRegister(void) {
|
||||
iocshRegister(&configEl734CreateAxis, configEl734CreateAxisCallFunc);
|
||||
}
|
||||
epicsExportRegistrar(el734AxisRegister);
|
||||
|
||||
} // extern "C"
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
#ifndef el734AXIS_H
|
||||
#define el734AXIS_H
|
||||
#include "el734Controller.h"
|
||||
#include "sinqController.h"
|
||||
#include <memory>
|
||||
|
||||
struct HIDDEN el734AxisImpl;
|
||||
|
||||
class HIDDEN el734Axis : public sinqAxis {
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new el734Axis
|
||||
*
|
||||
* @param pController Pointer to the associated controller
|
||||
* @param axisNo Index of the axis
|
||||
* @param initialize By setting this parameter to false, the
|
||||
* initialization functions of the axes are not executed. This is e.g.
|
||||
* necessary when this constructor is called from a children class
|
||||
* constructor which performs its own initialization.
|
||||
*/
|
||||
el734Axis(el734Controller *pController, int axisNo, bool initialize = true);
|
||||
|
||||
/**
|
||||
* @brief Destroy the el734Axis
|
||||
*
|
||||
* This destructor is necessary in order to use the PIMPL idiom.
|
||||
*/
|
||||
virtual ~el734Axis();
|
||||
|
||||
/**
|
||||
* @brief Readout of some values from the controller at IOC startup
|
||||
*
|
||||
* The following steps are performed:
|
||||
* - Read out the motor status, motor position, velocity and acceleration
|
||||
* from the MCU and store this information in the parameter library.
|
||||
* - Set the enable PV according to the initial status of the axis.
|
||||
*
|
||||
* @return asynStatus
|
||||
*/
|
||||
virtual asynStatus init();
|
||||
|
||||
/**
|
||||
* @brief Implementation of the `stop` function from asynMotorAxis
|
||||
*
|
||||
* @param acceleration Acceleration ACCEL from the motor record. This
|
||||
* value is currently not used.
|
||||
* @return asynStatus
|
||||
*/
|
||||
virtual asynStatus stop(double acceleration);
|
||||
|
||||
/**
|
||||
* @brief Implementation of the `doHome` function from sinqAxis. The
|
||||
* parameters are described in the documentation of `sinqAxis::doHome`.
|
||||
*
|
||||
* @param minVelocity
|
||||
* @param maxVelocity
|
||||
* @param acceleration
|
||||
* @param forwards
|
||||
* @return asynStatus
|
||||
*/
|
||||
virtual asynStatus doHome(double minVelocity, double maxVelocity,
|
||||
double acceleration, int forwards);
|
||||
|
||||
/**
|
||||
* @brief Implementation of the `doPoll` function from sinqAxis. The
|
||||
* parameters are described in the documentation of `sinqAxis::doPoll`.
|
||||
*
|
||||
* @param moving
|
||||
* @return asynStatus
|
||||
*/
|
||||
virtual asynStatus doPoll(bool *moving);
|
||||
|
||||
/**
|
||||
* @brief Implementation of the `doMove` function from sinqAxis. The
|
||||
* parameters are described in the documentation of `sinqAxis::doMove`.
|
||||
*
|
||||
* @param position
|
||||
* @param relative
|
||||
* @param min_velocity
|
||||
* @param max_velocity
|
||||
* @param acceleration
|
||||
* @return asynStatus
|
||||
*/
|
||||
virtual asynStatus doMove(double position, int relative,
|
||||
double min_velocity, double max_velocity,
|
||||
double acceleration);
|
||||
|
||||
/**
|
||||
* @brief Set the motor position
|
||||
*
|
||||
* If the motor has no encoder (encoderType == NoEncoder), its position is
|
||||
* stored within the controller itself and can be set to an arbitrary value
|
||||
* via this function. Otherwise, this function does nothing.
|
||||
*
|
||||
* @param position
|
||||
* @return asynStatus
|
||||
*/
|
||||
asynStatus setPosition(double position);
|
||||
|
||||
/**
|
||||
* @brief Implementation of the `doReset` function from sinqAxis.
|
||||
*
|
||||
* @param on
|
||||
* @return asynStatus
|
||||
*/
|
||||
virtual asynStatus doReset();
|
||||
|
||||
/**
|
||||
* @brief Read the encoder type (incremental or absolute) for this axis from
|
||||
* the MCU and store the information in the PV ENCODER_TYPE.
|
||||
*
|
||||
* @return asynStatus
|
||||
*/
|
||||
virtual asynStatus readEncoderType();
|
||||
|
||||
/**
|
||||
* @brief Return a pointer to the axis controller
|
||||
*/
|
||||
virtual el734Controller *pController() override { return pC_; };
|
||||
|
||||
/**
|
||||
* @brief Read the EL734 axis status and store the result in the fields of
|
||||
* the el734AxisImpl redefinition in el734Axis.cpp
|
||||
*
|
||||
*/
|
||||
asynStatus readAxisStatus();
|
||||
|
||||
private:
|
||||
el734Controller *pC_;
|
||||
std::unique_ptr<el734AxisImpl> pEl734A_;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,383 @@
|
||||
#include "el734Controller.h"
|
||||
#include "asynInt32SyncIO.h"
|
||||
#include "asynMotorController.h"
|
||||
#include "asynOctetSyncIO.h"
|
||||
#include "el734Axis.h"
|
||||
#include <epicsExport.h>
|
||||
#include <errlog.h>
|
||||
#include <initHooks.h>
|
||||
#include <iocsh.h>
|
||||
#include <netinet/in.h>
|
||||
#include <registryFunction.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
struct el734ControllerImpl {
|
||||
|
||||
// Timeout for the communication process in seconds
|
||||
double comTimeout;
|
||||
|
||||
char lastResponse[sinqController::MAXBUF_];
|
||||
|
||||
// User for writing int32 values to the port driver.
|
||||
asynUser *pasynInt32SyncIOipPort;
|
||||
|
||||
// Indices of additional ParamLib entries
|
||||
int limFromHardware;
|
||||
};
|
||||
#define NUM_el734_DRIVER_PARAMS 1
|
||||
|
||||
el734Controller::el734Controller(const char *portName,
|
||||
const char *ipPortConfigName, int numAxes,
|
||||
double movingPollPeriod, double idlePollPeriod,
|
||||
double comTimeout, int numExtraParams)
|
||||
: sinqController(portName, ipPortConfigName, numAxes, movingPollPeriod,
|
||||
idlePollPeriod, numExtraParams + NUM_el734_DRIVER_PARAMS)
|
||||
|
||||
{
|
||||
|
||||
// The paramLib indices are populated with the calls to createParam
|
||||
pEl734C_ = std::make_unique<el734ControllerImpl>((el734ControllerImpl){
|
||||
.comTimeout = comTimeout,
|
||||
.lastResponse = {0},
|
||||
.limFromHardware = 0,
|
||||
});
|
||||
|
||||
// Initialization of local variables
|
||||
asynStatus status = asynSuccess;
|
||||
|
||||
// Maximum allowed number of subsequent timeouts before the user is
|
||||
// informed.
|
||||
setMaxSubsequentTimeouts(10);
|
||||
|
||||
// =========================================================================
|
||||
// Create additional parameter library entries
|
||||
|
||||
status = createParam("LIM_FROM_HARDWARE", asynParamInt32,
|
||||
&pEl734C_->limFromHardware);
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(this->pasynUser(), ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\" => %s, line %d\nFATAL ERROR (creating a "
|
||||
"parameter failed with %s).\nTerminating IOC",
|
||||
portName, __PRETTY_FUNCTION__, __LINE__,
|
||||
stringifyAsynStatus(status));
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
/*
|
||||
The el734 controller expects a carriage return as terminator and terminates
|
||||
each reply with a carriage return (el734_manual.pdf, p. 58).
|
||||
*/
|
||||
pasynOctetSyncIO->setOutputEos(pasynUserController_, "\r", strlen("\r"));
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(this->pasynUser(), ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\" => %s, line %d\nFATAL ERROR "
|
||||
"(setting input EOS failed with %s).\nTerminating IOC",
|
||||
portName, __PRETTY_FUNCTION__, __LINE__,
|
||||
stringifyAsynStatus(status));
|
||||
pasynOctetSyncIO->disconnect(pasynOctetSyncIOipPort());
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
pasynOctetSyncIO->setInputEos(pasynUserController_, "\r", strlen("\r"));
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(this->pasynUser(), ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\" => %s, line %d\nFATAL ERROR "
|
||||
"(setting input EOS failed with %s).\nTerminating IOC",
|
||||
portName, __PRETTY_FUNCTION__, __LINE__,
|
||||
stringifyAsynStatus(status));
|
||||
pasynOctetSyncIO->disconnect(pasynOctetSyncIOipPort());
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
status = callParamCallbacks();
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(this->pasynUser(), ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\" => %s, line %d\nFATAL ERROR "
|
||||
"(executing ParamLib callbacks failed "
|
||||
"with %s).\nTerminating IOC",
|
||||
portName, __PRETTY_FUNCTION__, __LINE__,
|
||||
stringifyAsynStatus(status));
|
||||
pasynOctetSyncIO->disconnect(pasynOctetSyncIOipPort());
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
// =========================================================================;
|
||||
|
||||
/*
|
||||
We try to connect to the port via the port name provided by the constructor.
|
||||
If this fails, the function is terminated via exit.
|
||||
*/
|
||||
pasynInt32SyncIO->connect(ipPortConfigName, 0,
|
||||
&pEl734C_->pasynInt32SyncIOipPort, NULL);
|
||||
if (status != asynSuccess || pEl734C_->pasynInt32SyncIOipPort == nullptr) {
|
||||
errlogPrintf("Controller \"%s\" => %s, line %d:\nFATAL ERROR (cannot "
|
||||
"connect to MCU controller).\n"
|
||||
"Terminating IOC",
|
||||
portName, __PRETTY_FUNCTION__, __LINE__);
|
||||
pasynOctetSyncIO->disconnect(pasynOctetSyncIOipPort());
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
el734Controller::~el734Controller() {}
|
||||
|
||||
/*
|
||||
Access one of the axes of the controller via the axis adress stored in asynUser.
|
||||
If the axis does not exist or is not a Axis, a nullptr is returned and an
|
||||
error is emitted.
|
||||
*/
|
||||
el734Axis *el734Controller::getEl734Axis(asynUser *pasynUser) {
|
||||
asynMotorAxis *asynAxis = asynMotorController::getAxis(pasynUser);
|
||||
return dynamic_cast<el734Axis *>(asynAxis);
|
||||
}
|
||||
|
||||
/*
|
||||
Access one of the axes of the controller via the axis index.
|
||||
If the axis does not exist or is not a Axis, the function must return Null
|
||||
*/
|
||||
el734Axis *el734Controller::getEl734Axis(int axisNo) {
|
||||
asynMotorAxis *asynAxis = asynMotorController::getAxis(axisNo);
|
||||
return dynamic_cast<el734Axis *>(asynAxis);
|
||||
}
|
||||
|
||||
asynStatus el734Controller::writeRead(int axisNo, const char *command,
|
||||
char *response) {
|
||||
|
||||
// Definition of local variables.
|
||||
asynStatus status = asynSuccess;
|
||||
asynStatus timeoutStatus = asynSuccess;
|
||||
char drvMessageText[MAXBUF_] = {0};
|
||||
int motorStatusProblem = 0;
|
||||
int numReceivedResponses = 0;
|
||||
|
||||
/*
|
||||
asyn defines the following reasons for an end-of-message coming from the MCU
|
||||
(https://epics.anl.gov/modules/soft/asyn/R4-14/asynDriver.pdf, p. 28):
|
||||
0: Timeout
|
||||
1: Request count reached
|
||||
2: End of string detected -> In this driver, this is the "normal" case
|
||||
4: End indicator detected
|
||||
Combinations of reasons are also possible, e.g. eomReason = 5 would mean
|
||||
that both the request count was reached and an end indicator was detected.
|
||||
*/
|
||||
int eomReason = 0;
|
||||
|
||||
// Number of bytes of the outgoing message (which is command + the
|
||||
// end-of-string terminator defined in the constructor)
|
||||
size_t nbytesOut = 0;
|
||||
|
||||
// Number of bytes of the incoming message (which is response + the
|
||||
// end-of-string terminator defined in the constructor)
|
||||
size_t nbytesIn = 0;
|
||||
|
||||
// =========================================================================
|
||||
|
||||
el734Axis *axis = getEl734Axis(axisNo);
|
||||
if (axis == nullptr) {
|
||||
// We already did the error logging directly in getAxis
|
||||
return asynError;
|
||||
}
|
||||
const size_t commandLength = strlen(command);
|
||||
|
||||
/*
|
||||
The writeRead command performs the following steps:
|
||||
1) Flush the socket buffer on the IOC side (not the controller!)
|
||||
2) Write a command to the controller
|
||||
3) Read the response
|
||||
|
||||
If a timeout occurs during writing or reading, inform the user that we're
|
||||
trying to reconnect. If the problem persists, ask them to call the support
|
||||
*/
|
||||
status = pasynOctetSyncIO->writeRead(
|
||||
pasynOctetSyncIOipPort(), command, commandLength, response, MAXBUF_,
|
||||
pEl734C_->comTimeout, &nbytesOut, &nbytesIn, &eomReason);
|
||||
|
||||
/*
|
||||
Check if the return message is ?LOC - this means that the controller is in
|
||||
local mode and does not accept commands from the driver. Report an error
|
||||
and return
|
||||
*/
|
||||
if (strstr(response, "?LOC") != NULL) {
|
||||
if (getMsgPrintControl().shouldBePrinted(portName, axisNo,
|
||||
__PRETTY_FUNCTION__, __LINE__,
|
||||
true, pasynUser())) {
|
||||
asynPrint(
|
||||
this->pasynUser(), ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\", axis %d => %s, line %d\nController is in "
|
||||
"local mode, use the RESET PV to put it into remote mode.%s\n",
|
||||
portName, axisNo, __PRETTY_FUNCTION__, __LINE__,
|
||||
getMsgPrintControl().getSuffix());
|
||||
}
|
||||
|
||||
snprintf(drvMessageText, sizeof(drvMessageText),
|
||||
"Controller is in local mode, use the reset button to put it "
|
||||
"into remote mode.");
|
||||
status = asynError;
|
||||
} else if (strstr(response, "?CMD") != NULL) {
|
||||
if (getMsgPrintControl().shouldBePrinted(portName, axisNo,
|
||||
__PRETTY_FUNCTION__, __LINE__,
|
||||
true, pasynUser())) {
|
||||
asynPrint(this->pasynUser(), ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\", axis %d => %s, line %d\nCould not "
|
||||
"interpret command %s.%s\n",
|
||||
portName, axisNo, __PRETTY_FUNCTION__, __LINE__, command,
|
||||
getMsgPrintControl().getSuffix());
|
||||
}
|
||||
|
||||
snprintf(drvMessageText, sizeof(drvMessageText),
|
||||
"Could not interpret command %s. Please call the support.",
|
||||
command);
|
||||
status = asynError;
|
||||
} else if (strstr(response, "?PAR") != NULL) {
|
||||
if (getMsgPrintControl().shouldBePrinted(portName, axisNo,
|
||||
__PRETTY_FUNCTION__, __LINE__,
|
||||
true, pasynUser())) {
|
||||
asynPrint(this->pasynUser(), ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\", axis %d => %s, line %d\nInvalid "
|
||||
"parameters in command %s.%s\n",
|
||||
portName, axisNo, __PRETTY_FUNCTION__, __LINE__, command,
|
||||
getMsgPrintControl().getSuffix());
|
||||
}
|
||||
|
||||
snprintf(drvMessageText, sizeof(drvMessageText),
|
||||
"Invalid parameters in command %s. Please call the support.",
|
||||
command);
|
||||
status = asynError;
|
||||
} else if (strstr(response, "?RNG") != NULL) {
|
||||
if (getMsgPrintControl().shouldBePrinted(portName, axisNo,
|
||||
__PRETTY_FUNCTION__, __LINE__,
|
||||
true, pasynUser())) {
|
||||
asynPrint(this->pasynUser(), ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\", axis %d => %s, line %d\nParameter in "
|
||||
"command %s out of range.%s\n",
|
||||
portName, axisNo, __PRETTY_FUNCTION__, __LINE__, command,
|
||||
getMsgPrintControl().getSuffix());
|
||||
}
|
||||
|
||||
snprintf(
|
||||
drvMessageText, sizeof(drvMessageText),
|
||||
"Parameter in command %s out of range. Please call the support.",
|
||||
command);
|
||||
status = asynError;
|
||||
}
|
||||
|
||||
// Create custom error messages for different failure modes, if no error
|
||||
// message has been set yet
|
||||
if (strlen(drvMessageText) == 0) {
|
||||
switch (status) {
|
||||
case asynSuccess:
|
||||
break; // Communicate nothing
|
||||
case asynTimeout:
|
||||
snprintf(drvMessageText, sizeof(drvMessageText),
|
||||
"connection timeout for axis %d", axisNo);
|
||||
break;
|
||||
case asynDisconnected:
|
||||
snprintf(drvMessageText, sizeof(drvMessageText),
|
||||
"axis is not connected");
|
||||
break;
|
||||
case asynDisabled:
|
||||
snprintf(drvMessageText, sizeof(drvMessageText),
|
||||
"axis is disabled");
|
||||
break;
|
||||
default:
|
||||
snprintf(drvMessageText, sizeof(drvMessageText),
|
||||
"Communication failed (%s)", stringifyAsynStatus(status));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Log the overall status (communication successfull or not)
|
||||
if (status == asynSuccess) {
|
||||
setAxisParamChecked(axis, motorStatusCommsError, false);
|
||||
} else {
|
||||
// Check if the axis already is in an error communication mode. If
|
||||
// it is not, upstream the error. This is done to avoid "flooding"
|
||||
// the user with different error messages if more than one error
|
||||
// ocurred before an error-free communication
|
||||
getAxisParamChecked(axis, motorStatusProblem, &motorStatusProblem);
|
||||
|
||||
if (motorStatusProblem == 0) {
|
||||
setAxisParamChecked(axis, motorMessageText, drvMessageText);
|
||||
setAxisParamChecked(axis, motorStatusProblem, true);
|
||||
setAxisParamChecked(axis, motorStatusCommsError, true);
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
int el734Controller::limFromHardware() { return pEl734C_->limFromHardware; }
|
||||
|
||||
asynUser *el734Controller::pasynInt32SyncIOipPort() {
|
||||
return pEl734C_->pasynInt32SyncIOipPort;
|
||||
}
|
||||
|
||||
/*************************************************************************************/
|
||||
/** The following functions are C-wrappers, and can be called directly from
|
||||
* iocsh */
|
||||
|
||||
extern "C" {
|
||||
|
||||
/*
|
||||
C wrapper for the controller constructor. Please refer to the
|
||||
el734Controller constructor documentation.
|
||||
*/
|
||||
asynStatus el734CreateController(const char *portName,
|
||||
const char *ipPortConfigName, int numAxes,
|
||||
double movingPollPeriod, double idlePollPeriod,
|
||||
double comTimeout) {
|
||||
/*
|
||||
We create a new instance of the controller, using the "new" keyword to
|
||||
allocate it on the heap while avoiding RAII.
|
||||
https://github.com/epics-modules/motor/blob/master/motorApp/MotorSrc/asynMotorController.cpp
|
||||
https://github.com/epics-modules/asyn/blob/master/asyn/asynPortDriver/asynPortDriver.cpp
|
||||
|
||||
The created object is registered in EPICS in its constructor and can
|
||||
safely be "leaked" here.
|
||||
*/
|
||||
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
|
||||
#pragma GCC diagnostic ignored "-Wunused-variable"
|
||||
el734Controller *pController =
|
||||
new el734Controller(portName, ipPortConfigName, numAxes,
|
||||
movingPollPeriod, idlePollPeriod, comTimeout);
|
||||
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
/*
|
||||
Define name and type of the arguments for the CreateController function
|
||||
in the iocsh. This is done by creating structs with the argument names and
|
||||
types and then providing "factory" functions
|
||||
(configCreateControllerCallFunc). These factory functions are used to
|
||||
register the constructors during compilation.
|
||||
*/
|
||||
static const iocshArg CreateControllerArg0 = {"Controller name (e.g. mcu1)",
|
||||
iocshArgString};
|
||||
static const iocshArg CreateControllerArg1 = {"Asyn IP port name (e.g. pmcu1)",
|
||||
iocshArgString};
|
||||
static const iocshArg CreateControllerArg2 = {"Number of axes", iocshArgInt};
|
||||
static const iocshArg CreateControllerArg3 = {"Moving poll rate (s)",
|
||||
iocshArgDouble};
|
||||
static const iocshArg CreateControllerArg4 = {"Idle poll rate (s)",
|
||||
iocshArgDouble};
|
||||
static const iocshArg CreateControllerArg5 = {"Communication timeout (s)",
|
||||
iocshArgDouble};
|
||||
static const iocshArg *const CreateControllerArgs[] = {
|
||||
&CreateControllerArg0, &CreateControllerArg1, &CreateControllerArg2,
|
||||
&CreateControllerArg3, &CreateControllerArg4, &CreateControllerArg5};
|
||||
static const iocshFuncDef configEl734CreateController = {"el734Controller", 6,
|
||||
CreateControllerArgs};
|
||||
static void configEl734CreateControllerCallFunc(const iocshArgBuf *args) {
|
||||
el734CreateController(args[0].sval, args[1].sval, args[2].ival,
|
||||
args[3].dval, args[4].dval, args[5].dval);
|
||||
}
|
||||
|
||||
// This function is made known to EPICS in turboPmac.dbd and is called by
|
||||
// EPICS in order to register both functions in the IOC shell
|
||||
static void el734ControllerRegister(void) {
|
||||
iocshRegister(&configEl734CreateController,
|
||||
configEl734CreateControllerCallFunc);
|
||||
}
|
||||
epicsExportRegistrar(el734ControllerRegister);
|
||||
|
||||
} // extern "C"
|
||||
@@ -0,0 +1,82 @@
|
||||
#ifndef el734Controller_H
|
||||
#define el734Controller_H
|
||||
#include "sinqAxis.h"
|
||||
#include "sinqController.h"
|
||||
#include <memory>
|
||||
|
||||
// Forward declaration of the controller class to resolve the cyclic dependency
|
||||
// between the controller and the axis .h-file. See
|
||||
// https://en.cppreference.com/w/cpp/language/class.
|
||||
class HIDDEN el734Axis;
|
||||
|
||||
struct HIDDEN el734ControllerImpl;
|
||||
|
||||
class HIDDEN el734Controller : public sinqController {
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new el734Controller object. This function is meant
|
||||
to be called from a child class constructor.
|
||||
*
|
||||
* @param portName See sinqController constructor
|
||||
* @param ipPortConfigName See sinqController constructor
|
||||
* @param numAxes See sinqController constructor
|
||||
* @param movingPollPeriod See sinqController constructor
|
||||
* @param idlePollPeriod See sinqController constructor
|
||||
* @param comTimeout When trying to communicate with the device,
|
||||
the underlying asynOctetSyncIO interface waits for a response until this
|
||||
time (in seconds) has passed, then it declares a timeout.
|
||||
* @param numExtraParams Number of extra parameters from a child class
|
||||
*/
|
||||
el734Controller(const char *portName, const char *ipPortConfigName,
|
||||
int numAxes, double movingPollPeriod, double idlePollPeriod,
|
||||
double comTimeout, int numExtraParams = 0);
|
||||
|
||||
/**
|
||||
* @brief Destroy the controller. Its implementation is empty, however the
|
||||
* destructor needs to be provided for handling turboPmacControllerImpl.
|
||||
*
|
||||
*/
|
||||
virtual ~el734Controller();
|
||||
|
||||
/**
|
||||
* @brief Get the axis object.
|
||||
*
|
||||
* @param pasynUser Specify the axis via the asynUser
|
||||
* @return turboPmacAxis* If no axis could be found, this is a
|
||||
* nullptr
|
||||
*/
|
||||
el734Axis *getEl734Axis(asynUser *pasynUser);
|
||||
|
||||
/**
|
||||
* @brief Get the axis object.
|
||||
*
|
||||
* @param axisNo Specify the axis via its index
|
||||
* @return turboPmacAxis* If no axis could be found, this is a
|
||||
* nullptr
|
||||
*/
|
||||
el734Axis *getEl734Axis(int axisNo);
|
||||
|
||||
/**
|
||||
* @brief Send a command to the hardware and receive a response
|
||||
*
|
||||
* @param axisNo Axis to which the command should be send
|
||||
* @param command Command for the hardware
|
||||
* @param response Buffer for the response. This buffer is
|
||||
* expected to have the size MAXBUF_.
|
||||
* @return asynStatus
|
||||
*/
|
||||
asynStatus writeRead(int axisNo, const char *command, char *response)
|
||||
__attribute__((visibility("hidden")));
|
||||
|
||||
// Accessors for additional PVs
|
||||
int limFromHardware();
|
||||
|
||||
asynUser *pasynInt32SyncIOipPort();
|
||||
|
||||
private:
|
||||
std::unique_ptr<el734ControllerImpl> pEl734C_;
|
||||
|
||||
protected:
|
||||
};
|
||||
|
||||
#endif /* el734Controller_H */
|
||||
Reference in New Issue
Block a user