Initial commit for EL734 driver based on sinqMotor
Test And Build / Lint (push) Failing after 4s
Test And Build / Build (push) Successful in 7s

This commit is contained in:
2025-11-03 09:31:44 +01:00
commit 39dc6c5a5a
19 changed files with 2496 additions and 0 deletions
BIN
View File
Binary file not shown.
+33
View File
@@ -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
}
+49
View File
@@ -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
}
View File
+2
View File
@@ -0,0 +1,2 @@
registrar(el734ControllerRegister)
registrar(el734AxisRegister)
+792
View File
@@ -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", &currentPosition);
// 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
View File
@@ -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
+383
View File
@@ -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"
+82
View File
@@ -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 */