Compare commits
2 Commits
master
...
pmacV3-rew
Author | SHA1 | Date | |
---|---|---|---|
b700fca383 | |||
fd763a26ae |
@ -13,7 +13,7 @@ REQUIRED+=scaler
|
|||||||
REQUIRED+=asynMotor
|
REQUIRED+=asynMotor
|
||||||
|
|
||||||
# Release version
|
# Release version
|
||||||
LIBVERSION=2024-dev
|
LIBVERSION=2024-newPmacV3
|
||||||
|
|
||||||
# DB files to include in the release
|
# DB files to include in the release
|
||||||
TEMPLATES += sinqEPICSApp/Db/dimetix.db
|
TEMPLATES += sinqEPICSApp/Db/dimetix.db
|
||||||
@ -38,6 +38,9 @@ SOURCES += sinqEPICSApp/src/pmacController.cpp
|
|||||||
SOURCES += sinqEPICSApp/src/MasterMACSDriver.cpp
|
SOURCES += sinqEPICSApp/src/MasterMACSDriver.cpp
|
||||||
SOURCES += sinqEPICSApp/src/C804Axis.cpp
|
SOURCES += sinqEPICSApp/src/C804Axis.cpp
|
||||||
SOURCES += sinqEPICSApp/src/C804Controller.cpp
|
SOURCES += sinqEPICSApp/src/C804Controller.cpp
|
||||||
|
SOURCES += sinqEPICSApp/src/newPmacV3Axis.cpp
|
||||||
|
SOURCES += sinqEPICSApp/src/newPmacV3Controller.cpp
|
||||||
|
SOURCES += sinqEPICSApp/src/pmacController.cpp
|
||||||
|
|
||||||
USR_CFLAGS += -Wall -Wextra # -Werror
|
USR_CFLAGS += -Wall -Wextra # -Werror
|
||||||
|
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
#include "C804Axis.h"
|
#include "C804Axis.h"
|
||||||
#include "C804Controller.h"
|
#include "C804Controller.h"
|
||||||
#include <errlog.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <math.h>
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <unistd.h>
|
#include <errlog.h>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
#include <math.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
C804Axis::C804Axis(C804Controller *pC, int axisNo) : SINQAxis(pC, axisNo), pC_(pC)
|
C804Axis::C804Axis(C804Controller *pC, int axisNo)
|
||||||
{
|
: SINQAxis(pC, axisNo), pC_(pC) {
|
||||||
/*
|
/*
|
||||||
The superclass constructor SINQAxis calls in turn its superclass constructor
|
The superclass constructor SINQAxis calls in turn its superclass constructor
|
||||||
asynMotorAxis. In the latter, a pointer to the constructed object this is
|
asynMotorAxis. In the latter, a pointer to the constructed object this is
|
||||||
@ -16,13 +16,14 @@ C804Axis::C804Axis(C804Controller *pC, int axisNo) : SINQAxis(pC, axisNo), pC_(p
|
|||||||
|
|
||||||
pC->pAxes_[axisNo] = this;
|
pC->pAxes_[axisNo] = this;
|
||||||
|
|
||||||
Therefore, the axes are managed by the controller pC. See C804Controller.cpp for further explanation.
|
Therefore, the axes are managed by the controller pC. See C804Controller.cpp
|
||||||
If axisNo is out of bounds, asynMotorAxis prints an error (see
|
for further explanation. If axisNo is out of bounds, asynMotorAxis prints an
|
||||||
https://github.com/epics-modules/motor/blob/master/motorApp/MotorSrc/asynMotorAxis.cpp, line 40).
|
error (see
|
||||||
However, we want the IOC creation to stop completely, since this is a configuration error.
|
https://github.com/epics-modules/motor/blob/master/motorApp/MotorSrc/asynMotorAxis.cpp,
|
||||||
|
line 40). However, we want the IOC creation to stop completely, since this
|
||||||
|
is a configuration error.
|
||||||
*/
|
*/
|
||||||
if (axisNo >= pC->numAxes_)
|
if (axisNo >= pC->numAxes_) {
|
||||||
{
|
|
||||||
exit(-1);
|
exit(-1);
|
||||||
}
|
}
|
||||||
last_position_steps_ = 0;
|
last_position_steps_ = 0;
|
||||||
@ -30,8 +31,7 @@ C804Axis::C804Axis(C804Controller *pC, int axisNo) : SINQAxis(pC, axisNo), pC_(p
|
|||||||
last_poll_ = 0.0;
|
last_poll_ = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
C804Axis::~C804Axis(void)
|
C804Axis::~C804Axis(void) {
|
||||||
{
|
|
||||||
// Since the controller memory is managed somewhere else, we don't need to
|
// Since the controller memory is managed somewhere else, we don't need to
|
||||||
// clean up the pointer pC here.
|
// clean up the pointer pC here.
|
||||||
}
|
}
|
||||||
@ -39,47 +39,44 @@ C804Axis::~C804Axis(void)
|
|||||||
/*
|
/*
|
||||||
The polling function informs us about the state of the axis, in particular if it
|
The polling function informs us about the state of the axis, in particular if it
|
||||||
is currently moving. It is called periodically, with the period defined by
|
is currently moving. It is called periodically, with the period defined by
|
||||||
the controller constructor arguments idlePollPeriod and movingPollPeriod depending on
|
the controller constructor arguments idlePollPeriod and movingPollPeriod
|
||||||
the current axis state.
|
depending on the current axis state.
|
||||||
*/
|
*/
|
||||||
asynStatus C804Axis::poll(bool *moving)
|
asynStatus C804Axis::poll(bool *moving) {
|
||||||
{
|
|
||||||
// Local variable declaration
|
// Local variable declaration
|
||||||
static const char *functionName = "C804Axis::poll";
|
static const char *functionName = "C804Axis::poll";
|
||||||
|
|
||||||
// The poll function is just a wrapper around poll_no_param_lib_update and handles mainly
|
// The poll function is just a wrapper around pollNoUpdate and
|
||||||
// the callParamCallbacks() function
|
// handles mainly the callParamCallbacks() function
|
||||||
asynStatus status_poll = C804Axis::poll_no_param_lib_update(moving);
|
asynStatus status_poll = C804Axis::pollNoUpdate(moving);
|
||||||
|
|
||||||
// According to the function documentation of asynMotorAxis::poll, this
|
// According to the function documentation of asynMotorAxis::poll, this
|
||||||
// function should be called at the end of a poll implementation.
|
// function should be called at the end of a poll implementation.
|
||||||
asynStatus status_callback = callParamCallbacks();
|
asynStatus status_callback = callParamCallbacks();
|
||||||
if (status_callback != asynSuccess)
|
if (status_callback != asynSuccess) {
|
||||||
{d
|
|
||||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
"%s: Updating the parameter library failed for axis %d\n", functionName, axisNo_);
|
"%s: Updating the parameter library failed for axis %d\n",
|
||||||
|
functionName, axisNo_);
|
||||||
return status_callback;
|
return status_callback;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
return status_poll;
|
return status_poll;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform the actual poll
|
// Perform the actual poll
|
||||||
asynStatus C804Axis::poll_no_param_lib_update(bool *moving)
|
asynStatus C804Axis::pollNoUpdate(bool *moving) {
|
||||||
{
|
|
||||||
// Local variable declaration
|
// Local variable declaration
|
||||||
static const char *functionName = "C804Axis::poll";
|
static const char *functionName = "C804Axis::poll";
|
||||||
asynStatus status;
|
asynStatus status;
|
||||||
int axis_status = 0;
|
int axis_status = 0;
|
||||||
|
|
||||||
// The controller returns the position and velocity in encoder steps.
|
// The controller returns the position and velocity in encoder steps.
|
||||||
// This value needs to be converted in user units (engineering units EGU) via
|
// This value needs to be converted in user units (engineering units EGU)
|
||||||
// the record field MRES of the motor record. This field has already been read
|
// via the record field MRES of the motor record. This field has already
|
||||||
// by the constructor into the member variable motorRecResolution_.
|
// been read by the constructor into the member variable
|
||||||
// To go from steps to user units, multiply with motorRecResolution_
|
// motorRecResolution_. To go from steps to user units, multiply with
|
||||||
// Example: If 10 steps correspond to 1 mm, MRES should be 0.1.
|
// motorRecResolution_ Example: If 10 steps correspond to 1 mm, MRES should
|
||||||
|
// be 0.1.
|
||||||
int position_error_steps = 0;
|
int position_error_steps = 0;
|
||||||
int motor_position_steps = 0;
|
int motor_position_steps = 0;
|
||||||
int motor_velocity_steps = 0;
|
int motor_velocity_steps = 0;
|
||||||
@ -89,28 +86,28 @@ asynStatus C804Axis::poll_no_param_lib_update(bool *moving)
|
|||||||
double motor_velocity = .0;
|
double motor_velocity = .0;
|
||||||
double programmed_motor_velocity = .0;
|
double programmed_motor_velocity = .0;
|
||||||
|
|
||||||
// The buffer sizes for command and response are defined in the controller (see the corresponding source code files)
|
// The buffer sizes for command and response are defined in the controller
|
||||||
|
// (see the corresponding source code files)
|
||||||
char command[pC_->C804_MAXBUF_], response[pC_->C804_MAXBUF_];
|
char command[pC_->C804_MAXBUF_], response[pC_->C804_MAXBUF_];
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Cancel the poll if the last poll has "just" happened.
|
Cancel the poll if the last poll has "just" happened.
|
||||||
*/
|
*/
|
||||||
if (time(NULL) < last_poll_ + 0.5 * pC_->movingPollPeriod_)
|
if (time(NULL) < last_poll_ + 0.5 * pC_->movingPollPeriod_) {
|
||||||
{
|
|
||||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_WARNING,
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_WARNING,
|
||||||
"%s: Aborted poll since the last poll for axis %d happened a short time ago\n", functionName, axisNo_);
|
"%s: Aborted poll since the last poll for axis %d happened a "
|
||||||
|
"short time ago\n",
|
||||||
|
functionName, axisNo_);
|
||||||
return asynSuccess;
|
return asynSuccess;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
last_poll_ = time(NULL);
|
last_poll_ = time(NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
The parameter motorRecResolution_ is coupled to the field MRES of the motor
|
The parameter motorRecResolution_ is coupled to the field MRES of the motor
|
||||||
record in the following manner:
|
record in the following manner:
|
||||||
- In sinq_asyn_motor.db, the PV (motor_record_pv_name)MOTOR_REC_RESOLUTION is
|
- In sinq_asyn_motor.db, the PV (motor_record_pv_name)MOTOR_REC_RESOLUTION
|
||||||
defined as a copy of the field (motor_record_pv_name).MRES:
|
is defined as a copy of the field (motor_record_pv_name).MRES:
|
||||||
|
|
||||||
record(ao,"$(P)$(M):Resolution") {
|
record(ao,"$(P)$(M):Resolution") {
|
||||||
field(DESC, "$(M) resolution")
|
field(DESC, "$(M) resolution")
|
||||||
@ -121,27 +118,31 @@ asynStatus C804Axis::poll_no_param_lib_update(bool *moving)
|
|||||||
field(PREC, "$(PREC)")
|
field(PREC, "$(PREC)")
|
||||||
}
|
}
|
||||||
|
|
||||||
- The PV name MOTOR_REC_RESOLUTION is coupled in asynMotorController.h to the constant motorRecResolutionString
|
- The PV name MOTOR_REC_RESOLUTION is coupled in asynMotorController.h to
|
||||||
- ... which in turn is assigned to motorRecResolution_ in asynMotorController.cpp
|
the constant motorRecResolutionString
|
||||||
This way of making the field visible to the driver is described here: https://epics.anl.gov/tech-talk/2020/msg00378.php
|
- ... which in turn is assigned to motorRecResolution_ in
|
||||||
This is a one-way coupling, changes to the parameter library via setDoubleParam
|
asynMotorController.cpp This way of making the field visible to the driver
|
||||||
are NOT transferred to (motor_record_pv_name).MRES or to (motor_record_pv_name):Resolution.
|
is described here: https://epics.anl.gov/tech-talk/2020/msg00378.php This is
|
||||||
|
a one-way coupling, changes to the parameter library via setDoubleParam are
|
||||||
|
NOT transferred to (motor_record_pv_name).MRES or to
|
||||||
|
(motor_record_pv_name):Resolution.
|
||||||
|
|
||||||
NOTE: This function must not be called in the constructor (e.g. in order to
|
NOTE: This function must not be called in the constructor (e.g. in order to
|
||||||
save the read result to the member variable earlier), since the parameter library
|
save the read result to the member variable earlier), since the parameter
|
||||||
is updated at a later stage!
|
library is updated at a later stage!
|
||||||
*/
|
*/
|
||||||
pC_->getDoubleParam(axisNo_, pC_->motorRecResolution_, &motorRecResolution_);
|
pC_->getDoubleParam(axisNo_, pC_->motorRecResolution_,
|
||||||
|
&motorRecResolution_);
|
||||||
|
|
||||||
asynPrint(pC_->pasynUserSelf,ASYN_TRACE_FLOW,"Poll axis %d\n", axisNo_);
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW, "Polling axis %d\n",
|
||||||
|
axisNo_);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
We know that the motor resolution must not be zero. During the startup of the
|
We know that the motor resolution must not be zero. During the startup of
|
||||||
IOC, polls can happen before the record is fully initialized. In that case,
|
the IOC, polls can happen before the record is fully initialized. In that
|
||||||
all values are zero.
|
case, all values are zero.
|
||||||
*/
|
*/
|
||||||
if (motorRecResolution_ == 0)
|
if (motorRecResolution_ == 0) {
|
||||||
{
|
|
||||||
return asynError;
|
return asynError;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,11 +155,11 @@ asynStatus C804Axis::poll_no_param_lib_update(bool *moving)
|
|||||||
*/
|
*/
|
||||||
setIntegerParam(pC_->motorStatusProblem_, false);
|
setIntegerParam(pC_->motorStatusProblem_, false);
|
||||||
|
|
||||||
// Read out the position error of the axis (delta of target position to actual position)
|
// Read out the position error of the axis (delta of target position to
|
||||||
|
// actual position)
|
||||||
snprintf(command, pC_->C804_MAXBUF_ - 1, "%dTE", axisNo_);
|
snprintf(command, pC_->C804_MAXBUF_ - 1, "%dTE", axisNo_);
|
||||||
status = pC_->lowLevelWriteRead(axisNo_, command, response, true);
|
status = pC_->lowLevelWriteRead(axisNo_, command, response, true);
|
||||||
if (status == asynSuccess)
|
if (status == asynSuccess) {
|
||||||
{
|
|
||||||
int parsed_axis;
|
int parsed_axis;
|
||||||
sscanf(response, "%2dE%10d", &parsed_axis, &position_error_steps);
|
sscanf(response, "%2dE%10d", &parsed_axis, &position_error_steps);
|
||||||
|
|
||||||
@ -166,12 +167,12 @@ asynStatus C804Axis::poll_no_param_lib_update(bool *moving)
|
|||||||
position_error = double(position_error_steps) * motorRecResolution_;
|
position_error = double(position_error_steps) * motorRecResolution_;
|
||||||
|
|
||||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW,
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW,
|
||||||
"%s: Axis %d, response %s, position error %f\n", functionName, axisNo_, response, position_error);
|
"%s: Axis %d, response %s, position error %f\n", functionName,
|
||||||
}
|
axisNo_, response, position_error);
|
||||||
else
|
} else {
|
||||||
{
|
|
||||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
"%s: Reading the position error failed for axis %d\n", functionName, axisNo_);
|
"%s: Reading the position error failed for axis %d\n",
|
||||||
|
functionName, axisNo_);
|
||||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||||
|
|
||||||
// Stop the evaluation prematurely
|
// Stop the evaluation prematurely
|
||||||
@ -180,9 +181,9 @@ asynStatus C804Axis::poll_no_param_lib_update(bool *moving)
|
|||||||
|
|
||||||
// Read the current position.
|
// Read the current position.
|
||||||
snprintf(command, this->pC_->C804_MAXBUF_ - 1, "%dTP", this->axisNo_);
|
snprintf(command, this->pC_->C804_MAXBUF_ - 1, "%dTP", this->axisNo_);
|
||||||
status = this->pC_->lowLevelWriteRead(this->axisNo_, command, response, true);
|
status =
|
||||||
if (status == asynSuccess)
|
this->pC_->lowLevelWriteRead(this->axisNo_, command, response, true);
|
||||||
{
|
if (status == asynSuccess) {
|
||||||
int parsed_axis;
|
int parsed_axis;
|
||||||
sscanf(response, "%2dP%10d", &parsed_axis, &motor_position_steps);
|
sscanf(response, "%2dP%10d", &parsed_axis, &motor_position_steps);
|
||||||
|
|
||||||
@ -190,14 +191,14 @@ asynStatus C804Axis::poll_no_param_lib_update(bool *moving)
|
|||||||
motor_position = double(motor_position_steps) * motorRecResolution_;
|
motor_position = double(motor_position_steps) * motorRecResolution_;
|
||||||
|
|
||||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW,
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW,
|
||||||
"%s: Axis %d, response %s, position %f\n", functionName, axisNo_, response, motor_position);
|
"%s: Axis %d, response %s, position %f\n", functionName,
|
||||||
|
axisNo_, response, motor_position);
|
||||||
setDoubleParam(pC_->motorPosition_, motor_position);
|
setDoubleParam(pC_->motorPosition_, motor_position);
|
||||||
setDoubleParam(pC_->motorEncoderPosition_, motor_position);
|
setDoubleParam(pC_->motorEncoderPosition_, motor_position);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
"%s: Reading the position failed for axis %d\n", functionName, axisNo_);
|
"%s: Reading the position failed for axis %d\n", functionName,
|
||||||
|
axisNo_);
|
||||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
@ -205,9 +206,9 @@ asynStatus C804Axis::poll_no_param_lib_update(bool *moving)
|
|||||||
|
|
||||||
// Read the current velocity
|
// Read the current velocity
|
||||||
snprintf(command, this->pC_->C804_MAXBUF_ - 1, "%dTV", this->axisNo_);
|
snprintf(command, this->pC_->C804_MAXBUF_ - 1, "%dTV", this->axisNo_);
|
||||||
status = this->pC_->lowLevelWriteRead(this->axisNo_, command, response, true);
|
status =
|
||||||
if (status == asynSuccess)
|
this->pC_->lowLevelWriteRead(this->axisNo_, command, response, true);
|
||||||
{
|
if (status == asynSuccess) {
|
||||||
int parsed_axis;
|
int parsed_axis;
|
||||||
sscanf(response, "%2dV%10d", &parsed_axis, &motor_velocity_steps);
|
sscanf(response, "%2dV%10d", &parsed_axis, &motor_velocity_steps);
|
||||||
|
|
||||||
@ -215,12 +216,12 @@ asynStatus C804Axis::poll_no_param_lib_update(bool *moving)
|
|||||||
motor_velocity = double(motor_velocity_steps) * motorRecResolution_;
|
motor_velocity = double(motor_velocity_steps) * motorRecResolution_;
|
||||||
|
|
||||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW,
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW,
|
||||||
"%s: Axis %d, response %s, velocity %f\n", functionName, axisNo_, response, motor_velocity);
|
"%s: Axis %d, response %s, velocity %f\n", functionName,
|
||||||
}
|
axisNo_, response, motor_velocity);
|
||||||
else
|
} else {
|
||||||
{
|
|
||||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
"%s: Reading the velocity failed for axis %d\n", functionName, axisNo_);
|
"%s: Reading the velocity failed for axis %d\n", functionName,
|
||||||
|
axisNo_);
|
||||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
@ -228,23 +229,25 @@ asynStatus C804Axis::poll_no_param_lib_update(bool *moving)
|
|||||||
|
|
||||||
// Read the programmed velocity
|
// Read the programmed velocity
|
||||||
snprintf(command, this->pC_->C804_MAXBUF_ - 1, "%dTY", this->axisNo_);
|
snprintf(command, this->pC_->C804_MAXBUF_ - 1, "%dTY", this->axisNo_);
|
||||||
status = this->pC_->lowLevelWriteRead(this->axisNo_, command, response, true);
|
status =
|
||||||
if (status == asynSuccess)
|
this->pC_->lowLevelWriteRead(this->axisNo_, command, response, true);
|
||||||
{
|
if (status == asynSuccess) {
|
||||||
|
|
||||||
int parsed_axis;
|
int parsed_axis;
|
||||||
sscanf(response, "%2dY%10d", &parsed_axis, &programmed_motor_velocity_steps);
|
sscanf(response, "%2dY%10d", &parsed_axis,
|
||||||
|
&programmed_motor_velocity_steps);
|
||||||
|
|
||||||
// Scale from the encoder resultion to user units
|
// Scale from the encoder resultion to user units
|
||||||
programmed_motor_velocity = double(programmed_motor_velocity_steps) * motorRecResolution_;
|
programmed_motor_velocity =
|
||||||
|
double(programmed_motor_velocity_steps) * motorRecResolution_;
|
||||||
|
|
||||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW,
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW,
|
||||||
"%s: Axis %d, response %s, programmed velocity %f\n", functionName, axisNo_, response, programmed_motor_velocity);
|
"%s: Axis %d, response %s, programmed velocity %f\n",
|
||||||
}
|
functionName, axisNo_, response, programmed_motor_velocity);
|
||||||
else
|
} else {
|
||||||
{
|
|
||||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
"%s: Reading the programmed velocity failed for axis %d\n", functionName, axisNo_);
|
"%s: Reading the programmed velocity failed for axis %d\n",
|
||||||
|
functionName, axisNo_);
|
||||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
@ -253,15 +256,13 @@ asynStatus C804Axis::poll_no_param_lib_update(bool *moving)
|
|||||||
// Read the motor status
|
// Read the motor status
|
||||||
snprintf(command, pC_->C804_MAXBUF_ - 1, "%dTS", axisNo_);
|
snprintf(command, pC_->C804_MAXBUF_ - 1, "%dTS", axisNo_);
|
||||||
status = pC_->lowLevelWriteRead(this->axisNo_, command, response, true);
|
status = pC_->lowLevelWriteRead(this->axisNo_, command, response, true);
|
||||||
if (status == asynSuccess)
|
if (status == asynSuccess) {
|
||||||
{
|
|
||||||
int parsed_axis;
|
int parsed_axis;
|
||||||
sscanf(response, "%2dS%10d", &parsed_axis, &axis_status);
|
sscanf(response, "%2dS%10d", &parsed_axis, &axis_status);
|
||||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW,
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW,
|
||||||
"%s: Axis %d, response %s, status %d\n", functionName, axisNo_, response, axis_status);
|
"%s: Axis %d, response %s, status %d\n", functionName,
|
||||||
}
|
axisNo_, response, axis_status);
|
||||||
else
|
} else {
|
||||||
{
|
|
||||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
"%s: Reading the motor status %d\n", functionName, axisNo_);
|
"%s: Reading the motor status %d\n", functionName, axisNo_);
|
||||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||||
@ -270,24 +271,22 @@ asynStatus C804Axis::poll_no_param_lib_update(bool *moving)
|
|||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the axis is enabled by reading out bit 2 (see https://stackoverflow.com/questions/2249731/how-do-i-get-bit-by-bit-data-from-an-integer-value-in-c)
|
// Check if the axis is enabled by reading out bit 2 (see
|
||||||
|
// https://stackoverflow.com/questions/2249731/how-do-i-get-bit-by-bit-data-from-an-integer-value-in-c)
|
||||||
int mask = 1 << 2;
|
int mask = 1 << 2;
|
||||||
int masked_n = axis_status & mask;
|
int masked_n = axis_status & mask;
|
||||||
// Is 1 if the axis is disabled
|
// Is 1 if the axis is disabled
|
||||||
int disabled = masked_n >> 2;
|
int disabled = masked_n >> 2;
|
||||||
if (disabled)
|
if (disabled) {
|
||||||
{
|
|
||||||
enabled_ = false;
|
enabled_ = false;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
enabled_ = true;
|
enabled_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Determine if the motor is moving. This is determined by the following criteria:
|
Determine if the motor is moving. This is determined by the following
|
||||||
1) The motor position changes from poll to poll
|
criteria: 1) The motor position changes from poll to poll 2) The motor is
|
||||||
2) The motor is enabled
|
enabled
|
||||||
*/
|
*/
|
||||||
*moving = enabled_ && motor_position_steps != this->last_position_steps_;
|
*moving = enabled_ && motor_position_steps != this->last_position_steps_;
|
||||||
|
|
||||||
@ -300,22 +299,21 @@ asynStatus C804Axis::poll_no_param_lib_update(bool *moving)
|
|||||||
moving. If it has spent too much time in a moving state without reaching
|
moving. If it has spent too much time in a moving state without reaching
|
||||||
the target, stop the motor and return an error.
|
the target, stop the motor and return an error.
|
||||||
*/
|
*/
|
||||||
if (*moving)
|
if (*moving) {
|
||||||
{
|
|
||||||
|
|
||||||
int motorStatusMoving = 0;
|
int motorStatusMoving = 0;
|
||||||
pC_->getIntegerParam(axisNo_, pC_->motorStatusMoving_, &motorStatusMoving);
|
pC_->getIntegerParam(axisNo_, pC_->motorStatusMoving_,
|
||||||
|
&motorStatusMoving);
|
||||||
|
|
||||||
// motor is moving, but didn't move in the last poll
|
// motor is moving, but didn't move in the last poll
|
||||||
if (motorStatusMoving == 0)
|
if (motorStatusMoving == 0) {
|
||||||
{
|
|
||||||
time_t current_time = time(NULL);
|
time_t current_time = time(NULL);
|
||||||
|
|
||||||
// Factor 2 of the calculated moving time
|
// Factor 2 of the calculated moving time
|
||||||
estimatedArrivalTime_ = current_time + std::ceil(2 * std::fabs(position_error) / programmed_motor_velocity);
|
estimatedArrivalTime_ =
|
||||||
}
|
current_time + std::ceil(2 * std::fabs(position_error) /
|
||||||
else
|
programmed_motor_velocity);
|
||||||
{
|
} else {
|
||||||
// /*
|
// /*
|
||||||
// Motor is moving for a longer time than it should: Stop it
|
// Motor is moving for a longer time than it should: Stop it
|
||||||
// */
|
// */
|
||||||
@ -323,7 +321,9 @@ asynStatus C804Axis::poll_no_param_lib_update(bool *moving)
|
|||||||
// {
|
// {
|
||||||
// snprintf(command, pC_->C804_MAXBUF_ - 1, "%dST", axisNo_);
|
// snprintf(command, pC_->C804_MAXBUF_ - 1, "%dST", axisNo_);
|
||||||
// status = pC_->lowLevelWriteRead(axisNo_, command, response);
|
// status = pC_->lowLevelWriteRead(axisNo_, command, response);
|
||||||
// asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR, "%s: Stopped axis %d since it moved for double the time it should to reach its target\n", functionName, axisNo_);
|
// asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR, "%s: Stopped
|
||||||
|
// axis %d since it moved for double the time it should to reach
|
||||||
|
// its target\n", functionName, axisNo_);
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -339,54 +339,58 @@ asynStatus C804Axis::poll_no_param_lib_update(bool *moving)
|
|||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
asynStatus C804Axis::move(double position, int relative, double minVelocity, double maxVelocity, double acceleration)
|
asynStatus C804Axis::move(double position, int relative, double minVelocity,
|
||||||
{
|
double maxVelocity, double acceleration) {
|
||||||
asynStatus status;
|
asynStatus status;
|
||||||
static const char *functionName = "C804Axis::move";
|
static const char *functionName = "C804Axis::move";
|
||||||
char command[pC_->C804_MAXBUF_], response[pC_->C804_MAXBUF_];
|
char command[pC_->C804_MAXBUF_], response[pC_->C804_MAXBUF_];
|
||||||
double position_c_units = 0.0; // Controller units
|
double position_c_units = 0.0; // Controller units
|
||||||
int position_steps = 0;
|
int position_steps = 0;
|
||||||
|
|
||||||
// Convert from user coordinates (EGU) to controller coordinates (steps). Check for overflow
|
// Convert from user coordinates (EGU) to controller coordinates (steps).
|
||||||
if (motorRecResolution_ == 0.0)
|
// Check for overflow
|
||||||
{
|
if (motorRecResolution_ == 0.0) {
|
||||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR, "%s: MRES must not be zero. Movement is aborted", functionName);
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
|
"%s: MRES must not be zero. Movement is aborted",
|
||||||
|
functionName);
|
||||||
return asynError;
|
return asynError;
|
||||||
}
|
}
|
||||||
position_c_units = position / motorRecResolution_;
|
position_c_units = position / motorRecResolution_;
|
||||||
|
|
||||||
// Check for overflow during the division
|
// Check for overflow during the division
|
||||||
if (position_c_units * motorRecResolution_ != position)
|
if (position_c_units * motorRecResolution_ != position) {
|
||||||
{
|
asynPrint(
|
||||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
"%s: could not convert from user units (%f) to controller units (user units divided by resolution MRES %f) due to overflow.",
|
"%s: could not convert from user units (%f) to controller units "
|
||||||
|
"(user units divided by resolution MRES %f) due to overflow.",
|
||||||
functionName, position, motorRecResolution_);
|
functionName, position, motorRecResolution_);
|
||||||
return asynError;
|
return asynError;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Steps can only be integer values => cast to integer while checking for overflow
|
// Steps can only be integer values => cast to integer while checking for
|
||||||
if (std::numeric_limits<int>::max() < position_c_units || std::numeric_limits<int>::min() > position_c_units)
|
// overflow
|
||||||
{
|
if (std::numeric_limits<int>::max() < position_c_units ||
|
||||||
|
std::numeric_limits<int>::min() > position_c_units) {
|
||||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
"%s: target position %f cannot be converted to int (overflow). Check target value %f and MRES %f",
|
"%s: target position %f cannot be converted to int "
|
||||||
functionName, position_c_units, position_c_units, motorRecResolution_);
|
"(overflow). Check target value %f and MRES %f",
|
||||||
|
functionName, position_c_units, position_c_units,
|
||||||
|
motorRecResolution_);
|
||||||
return asynError;
|
return asynError;
|
||||||
}
|
}
|
||||||
position_steps = static_cast<int>(position_c_units);
|
position_steps = static_cast<int>(position_c_units);
|
||||||
|
|
||||||
// Convert from relative to absolute values
|
// Convert from relative to absolute values
|
||||||
if (relative)
|
if (relative) {
|
||||||
{
|
|
||||||
position_steps += last_position_steps_;
|
position_steps += last_position_steps_;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the axis is currently disabled, enable it
|
// If the axis is currently disabled, enable it
|
||||||
if (!enabled_)
|
if (!enabled_) {
|
||||||
{
|
|
||||||
snprintf(command, pC_->C804_MAXBUF_ - 1, "%dGO", axisNo_);
|
snprintf(command, pC_->C804_MAXBUF_ - 1, "%dGO", axisNo_);
|
||||||
status = pC_->lowLevelWriteRead(this->axisNo_, command, response, false);
|
status =
|
||||||
if (status != asynSuccess)
|
pC_->lowLevelWriteRead(this->axisNo_, command, response, false);
|
||||||
{
|
if (status != asynSuccess) {
|
||||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
"%s: Enabling axis %d\n failed", functionName, axisNo_);
|
"%s: Enabling axis %d\n failed", functionName, axisNo_);
|
||||||
return status;
|
return status;
|
||||||
@ -396,10 +400,10 @@ asynStatus C804Axis::move(double position, int relative, double minVelocity, dou
|
|||||||
// Start movement
|
// Start movement
|
||||||
snprintf(command, pC_->C804_MAXBUF_ - 1, "%dMA%d", axisNo_, position_steps);
|
snprintf(command, pC_->C804_MAXBUF_ - 1, "%dMA%d", axisNo_, position_steps);
|
||||||
status = pC_->lowLevelWriteRead(this->axisNo_, command, response, false);
|
status = pC_->lowLevelWriteRead(this->axisNo_, command, response, false);
|
||||||
if (status != asynSuccess)
|
if (status != asynSuccess) {
|
||||||
{
|
|
||||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
"%s: Setting the target position %d failed for axis %d\n", functionName, position_steps, axisNo_);
|
"%s: Setting the target position %d failed for axis %d\n",
|
||||||
|
functionName, position_steps, axisNo_);
|
||||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
@ -411,14 +415,13 @@ asynStatus C804Axis::move(double position, int relative, double minVelocity, dou
|
|||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
asynStatus C804Axis::moveVelocity(double min_velocity, double max_velocity, double acceleration)
|
asynStatus C804Axis::moveVelocity(double min_velocity, double max_velocity,
|
||||||
{
|
double acceleration) {
|
||||||
static const char *functionName = "C804Axis::moveVelocity";
|
static const char *functionName = "C804Axis::moveVelocity";
|
||||||
return asynError;
|
return asynError;
|
||||||
}
|
}
|
||||||
|
|
||||||
asynStatus C804Axis::stop(double acceleration)
|
asynStatus C804Axis::stop(double acceleration) {
|
||||||
{
|
|
||||||
asynStatus status = asynSuccess;
|
asynStatus status = asynSuccess;
|
||||||
static const char *functionName = "C804Axis::stop";
|
static const char *functionName = "C804Axis::stop";
|
||||||
char command[pC_->C804_MAXBUF_], response[pC_->C804_MAXBUF_];
|
char command[pC_->C804_MAXBUF_], response[pC_->C804_MAXBUF_];
|
||||||
@ -426,26 +429,28 @@ asynStatus C804Axis::stop(double acceleration)
|
|||||||
bool moving = false;
|
bool moving = false;
|
||||||
|
|
||||||
poll(&moving);
|
poll(&moving);
|
||||||
if (moving)
|
if (moving) {
|
||||||
{
|
|
||||||
// ST = Stop
|
// ST = Stop
|
||||||
snprintf(command, pC_->C804_MAXBUF_ - 1, "%dST", axisNo_);
|
snprintf(command, pC_->C804_MAXBUF_ - 1, "%dST", axisNo_);
|
||||||
status = pC_->lowLevelWriteRead(axisNo_, command, response, false);
|
status = pC_->lowLevelWriteRead(axisNo_, command, response, false);
|
||||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACEIO_DEVICE, "%s: Stop axis %d\n", functionName, axisNo_);
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACEIO_DEVICE, "%s: Stop axis %d\n",
|
||||||
|
functionName, axisNo_);
|
||||||
}
|
}
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
asynStatus C804Axis::home(double minVelocity, double maxVelocity, double acceleration, int forwards)
|
asynStatus C804Axis::home(double minVelocity, double maxVelocity,
|
||||||
{
|
double acceleration, int forwards) {
|
||||||
asynStatus status = asynSuccess;
|
asynStatus status = asynSuccess;
|
||||||
static const char *functionName = "C804Axis::home";
|
static const char *functionName = "C804Axis::home";
|
||||||
char command[pC_->C804_MAXBUF_], response[pC_->C804_MAXBUF_];
|
char command[pC_->C804_MAXBUF_], response[pC_->C804_MAXBUF_];
|
||||||
|
|
||||||
snprintf(command, pC_->C804_MAXBUF_ - 1, "%dFE0", axisNo_); // Home to the upper limit of the axis (25 mm)
|
snprintf(command, pC_->C804_MAXBUF_ - 1, "%dFE0",
|
||||||
|
axisNo_); // Home to the upper limit of the axis (25 mm)
|
||||||
status = pC_->lowLevelWriteRead(axisNo_, command, response, false);
|
status = pC_->lowLevelWriteRead(axisNo_, command, response, false);
|
||||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACEIO_DEVICE, "%s: Homing axis %d\n", functionName, axisNo_);
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACEIO_DEVICE, "%s: Homing axis %d\n",
|
||||||
|
functionName, axisNo_);
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
@ -453,23 +458,21 @@ asynStatus C804Axis::home(double minVelocity, double maxVelocity, double acceler
|
|||||||
/**
|
/**
|
||||||
If on is 0, disable the motor, otherwise enable it.
|
If on is 0, disable the motor, otherwise enable it.
|
||||||
*/
|
*/
|
||||||
asynStatus C804Axis::enable(int on)
|
asynStatus C804Axis::enable(int on) {
|
||||||
{
|
|
||||||
asynStatus status = asynSuccess;
|
asynStatus status = asynSuccess;
|
||||||
static const char *functionName = "C804Axis::enable";
|
static const char *functionName = "C804Axis::enable";
|
||||||
char command[pC_->C804_MAXBUF_], response[pC_->C804_MAXBUF_];
|
char command[pC_->C804_MAXBUF_], response[pC_->C804_MAXBUF_];
|
||||||
|
|
||||||
if (on == 0)
|
if (on == 0) {
|
||||||
{
|
|
||||||
snprintf(command, pC_->C804_MAXBUF_ - 1, "%dMF", axisNo_);
|
snprintf(command, pC_->C804_MAXBUF_ - 1, "%dMF", axisNo_);
|
||||||
status = pC_->lowLevelWriteRead(axisNo_, command, response, false);
|
status = pC_->lowLevelWriteRead(axisNo_, command, response, false);
|
||||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACEIO_DEVICE, "%s: Disable axis %d\n", functionName, axisNo_);
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACEIO_DEVICE,
|
||||||
}
|
"%s: Disable axis %d\n", functionName, axisNo_);
|
||||||
else
|
} else {
|
||||||
{
|
|
||||||
snprintf(command, pC_->C804_MAXBUF_ - 1, "%dMN", axisNo_);
|
snprintf(command, pC_->C804_MAXBUF_ - 1, "%dMN", axisNo_);
|
||||||
status = pC_->lowLevelWriteRead(axisNo_, command, response, false);
|
status = pC_->lowLevelWriteRead(axisNo_, command, response, false);
|
||||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACEIO_DEVICE, "%s: Enable axis %d\n", functionName, axisNo_);
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACEIO_DEVICE,
|
||||||
|
"%s: Enable axis %d\n", functionName, axisNo_);
|
||||||
}
|
}
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
@ -1,28 +1,31 @@
|
|||||||
#ifndef C804Axis_H
|
#ifndef C804Axis_H
|
||||||
#define C804Axis_H
|
#define C804Axis_H
|
||||||
|
|
||||||
#include "SINQController.h"
|
|
||||||
#include "SINQAxis.h"
|
#include "SINQAxis.h"
|
||||||
|
#include "SINQController.h"
|
||||||
|
|
||||||
// Forward declaration of the controller class to resolve the cyclic dependency
|
// Forward declaration of the controller class to resolve the cyclic dependency
|
||||||
// between C804Controller.h and C804Axis.h. See https://en.cppreference.com/w/cpp/language/class.
|
// between C804Controller.h and C804Axis.h. See
|
||||||
|
// https://en.cppreference.com/w/cpp/language/class.
|
||||||
class C804Controller;
|
class C804Controller;
|
||||||
|
|
||||||
class C804Axis : public SINQAxis
|
class C804Axis : public SINQAxis {
|
||||||
{
|
public:
|
||||||
public:
|
|
||||||
/* These are the methods we override from the base class */
|
/* These are the methods we override from the base class */
|
||||||
C804Axis(C804Controller *pController, int axisNo);
|
C804Axis(C804Controller *pController, int axisNo);
|
||||||
virtual ~C804Axis();
|
virtual ~C804Axis();
|
||||||
asynStatus move(double position, int relative, double min_velocity, double max_velocity, double acceleration);
|
asynStatus move(double position, int relative, double min_velocity,
|
||||||
asynStatus moveVelocity(double min_velocity, double max_velocity, double acceleration);
|
double max_velocity, double acceleration);
|
||||||
|
asynStatus moveVelocity(double min_velocity, double max_velocity,
|
||||||
|
double acceleration);
|
||||||
asynStatus stop(double acceleration);
|
asynStatus stop(double acceleration);
|
||||||
asynStatus home(double minVelocity, double maxVelocity, double acceleration, int forwards);
|
asynStatus home(double minVelocity, double maxVelocity, double acceleration,
|
||||||
|
int forwards);
|
||||||
asynStatus poll(bool *moving);
|
asynStatus poll(bool *moving);
|
||||||
asynStatus poll_no_param_lib_update(bool *moving);
|
asynStatus pollNoUpdate(bool *moving);
|
||||||
asynStatus enable(int on);
|
asynStatus enable(int on);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
C804Controller *pC_;
|
C804Controller *pC_;
|
||||||
|
|
||||||
void checkBounds(C804Controller *pController, int axisNo);
|
void checkBounds(C804Controller *pController, int axisNo);
|
||||||
@ -33,7 +36,7 @@ protected:
|
|||||||
int errorReported_;
|
int errorReported_;
|
||||||
bool enabled_;
|
bool enabled_;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class C804Controller;
|
friend class C804Controller;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -176,9 +176,7 @@ C804Controller::C804Controller(const char *portName, const char *lowLevelPortNam
|
|||||||
C804Controller::~C804Controller(void)
|
C804Controller::~C804Controller(void)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
Cleanup of the memory allocated in this->pAxes_. As discussed in the constructor,
|
Cleanup of the memory allocated in the asynMotorController constructor
|
||||||
this is not strictly necessary due to the way EPICS works, but it is good
|
|
||||||
practice anyway to properly clean up resources.
|
|
||||||
*/
|
*/
|
||||||
free(this->pAxes_);
|
free(this->pAxes_);
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,6 @@ public:
|
|||||||
/* These are the methods that we override */
|
/* These are the methods that we override */
|
||||||
C804Axis *getAxis(asynUser *pasynUser);
|
C804Axis *getAxis(asynUser *pasynUser);
|
||||||
C804Axis *getAxis(int axisNo);
|
C804Axis *getAxis(int axisNo);
|
||||||
C804Axis *castToC804Axis(asynMotorAxis *asynAxis);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
asynUser *lowLevelPortUser_;
|
asynUser *lowLevelPortUser_;
|
||||||
@ -26,6 +25,7 @@ protected:
|
|||||||
time_t idlePollPeriod_;
|
time_t idlePollPeriod_;
|
||||||
|
|
||||||
void log(const char *message);
|
void log(const char *message);
|
||||||
|
C804Axis *castToC804Axis(asynMotorAxis *asynAxis);
|
||||||
asynStatus lowLevelWriteRead(int axisNo, const char *command, char *response, bool expect_response);
|
asynStatus lowLevelWriteRead(int axisNo, const char *command, char *response, bool expect_response);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
246
sinqEPICSApp/src/clang-format
Normal file
246
sinqEPICSApp/src/clang-format
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
---
|
||||||
|
Language: Cpp
|
||||||
|
# BasedOnStyle: LLVM
|
||||||
|
AccessModifierOffset: -2
|
||||||
|
AlignAfterOpenBracket: Align
|
||||||
|
AlignArrayOfStructures: None
|
||||||
|
AlignConsecutiveAssignments:
|
||||||
|
Enabled: false
|
||||||
|
AcrossEmptyLines: false
|
||||||
|
AcrossComments: false
|
||||||
|
AlignCompound: false
|
||||||
|
AlignFunctionPointers: false
|
||||||
|
PadOperators: true
|
||||||
|
AlignConsecutiveBitFields:
|
||||||
|
Enabled: false
|
||||||
|
AcrossEmptyLines: false
|
||||||
|
AcrossComments: false
|
||||||
|
AlignCompound: false
|
||||||
|
AlignFunctionPointers: false
|
||||||
|
PadOperators: false
|
||||||
|
AlignConsecutiveDeclarations:
|
||||||
|
Enabled: false
|
||||||
|
AcrossEmptyLines: false
|
||||||
|
AcrossComments: false
|
||||||
|
AlignCompound: false
|
||||||
|
AlignFunctionPointers: false
|
||||||
|
PadOperators: false
|
||||||
|
AlignConsecutiveMacros:
|
||||||
|
Enabled: false
|
||||||
|
AcrossEmptyLines: false
|
||||||
|
AcrossComments: false
|
||||||
|
AlignCompound: false
|
||||||
|
AlignFunctionPointers: false
|
||||||
|
PadOperators: false
|
||||||
|
AlignConsecutiveShortCaseStatements:
|
||||||
|
Enabled: false
|
||||||
|
AcrossEmptyLines: false
|
||||||
|
AcrossComments: false
|
||||||
|
AlignCaseColons: false
|
||||||
|
AlignEscapedNewlines: Right
|
||||||
|
AlignOperands: Align
|
||||||
|
AlignTrailingComments:
|
||||||
|
Kind: Always
|
||||||
|
OverEmptyLines: 0
|
||||||
|
AllowAllArgumentsOnNextLine: true
|
||||||
|
AllowAllParametersOfDeclarationOnNextLine: true
|
||||||
|
AllowBreakBeforeNoexceptSpecifier: Never
|
||||||
|
AllowShortBlocksOnASingleLine: Never
|
||||||
|
AllowShortCaseLabelsOnASingleLine: false
|
||||||
|
AllowShortCompoundRequirementOnASingleLine: true
|
||||||
|
AllowShortEnumsOnASingleLine: true
|
||||||
|
AllowShortFunctionsOnASingleLine: All
|
||||||
|
AllowShortIfStatementsOnASingleLine: Never
|
||||||
|
AllowShortLambdasOnASingleLine: All
|
||||||
|
AllowShortLoopsOnASingleLine: false
|
||||||
|
AlwaysBreakAfterDefinitionReturnType: None
|
||||||
|
AlwaysBreakAfterReturnType: None
|
||||||
|
AlwaysBreakBeforeMultilineStrings: false
|
||||||
|
AlwaysBreakTemplateDeclarations: MultiLine
|
||||||
|
AttributeMacros:
|
||||||
|
- __capability
|
||||||
|
BinPackArguments: true
|
||||||
|
BinPackParameters: true
|
||||||
|
BitFieldColonSpacing: Both
|
||||||
|
BraceWrapping:
|
||||||
|
AfterCaseLabel: false
|
||||||
|
AfterClass: false
|
||||||
|
AfterControlStatement: Never
|
||||||
|
AfterEnum: false
|
||||||
|
AfterExternBlock: false
|
||||||
|
AfterFunction: false
|
||||||
|
AfterNamespace: false
|
||||||
|
AfterObjCDeclaration: false
|
||||||
|
AfterStruct: false
|
||||||
|
AfterUnion: false
|
||||||
|
BeforeCatch: false
|
||||||
|
BeforeElse: false
|
||||||
|
BeforeLambdaBody: false
|
||||||
|
BeforeWhile: false
|
||||||
|
IndentBraces: false
|
||||||
|
SplitEmptyFunction: true
|
||||||
|
SplitEmptyRecord: true
|
||||||
|
SplitEmptyNamespace: true
|
||||||
|
BreakAdjacentStringLiterals: true
|
||||||
|
BreakAfterAttributes: Leave
|
||||||
|
BreakAfterJavaFieldAnnotations: false
|
||||||
|
BreakArrays: true
|
||||||
|
BreakBeforeBinaryOperators: None
|
||||||
|
BreakBeforeConceptDeclarations: Always
|
||||||
|
BreakBeforeBraces: Attach
|
||||||
|
BreakBeforeInlineASMColon: OnlyMultiline
|
||||||
|
BreakBeforeTernaryOperators: true
|
||||||
|
BreakConstructorInitializers: BeforeColon
|
||||||
|
BreakInheritanceList: BeforeColon
|
||||||
|
BreakStringLiterals: true
|
||||||
|
ColumnLimit: 80
|
||||||
|
CommentPragmas: '^ IWYU pragma:'
|
||||||
|
CompactNamespaces: false
|
||||||
|
ConstructorInitializerIndentWidth: 4
|
||||||
|
ContinuationIndentWidth: 4
|
||||||
|
Cpp11BracedListStyle: true
|
||||||
|
DerivePointerAlignment: false
|
||||||
|
DisableFormat: false
|
||||||
|
EmptyLineAfterAccessModifier: Never
|
||||||
|
EmptyLineBeforeAccessModifier: LogicalBlock
|
||||||
|
ExperimentalAutoDetectBinPacking: false
|
||||||
|
FixNamespaceComments: true
|
||||||
|
ForEachMacros:
|
||||||
|
- foreach
|
||||||
|
- Q_FOREACH
|
||||||
|
- BOOST_FOREACH
|
||||||
|
IfMacros:
|
||||||
|
- KJ_IF_MAYBE
|
||||||
|
IncludeBlocks: Preserve
|
||||||
|
IncludeCategories:
|
||||||
|
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
|
||||||
|
Priority: 2
|
||||||
|
SortPriority: 0
|
||||||
|
CaseSensitive: false
|
||||||
|
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
|
||||||
|
Priority: 3
|
||||||
|
SortPriority: 0
|
||||||
|
CaseSensitive: false
|
||||||
|
- Regex: '.*'
|
||||||
|
Priority: 1
|
||||||
|
SortPriority: 0
|
||||||
|
CaseSensitive: false
|
||||||
|
IncludeIsMainRegex: '(Test)?$'
|
||||||
|
IncludeIsMainSourceRegex: ''
|
||||||
|
IndentAccessModifiers: false
|
||||||
|
IndentCaseBlocks: false
|
||||||
|
IndentCaseLabels: false
|
||||||
|
IndentExternBlock: AfterExternBlock
|
||||||
|
IndentGotoLabels: true
|
||||||
|
IndentPPDirectives: None
|
||||||
|
IndentRequiresClause: true
|
||||||
|
IndentWidth: 4
|
||||||
|
IndentWrappedFunctionNames: false
|
||||||
|
InsertBraces: false
|
||||||
|
InsertNewlineAtEOF: false
|
||||||
|
InsertTrailingCommas: None
|
||||||
|
IntegerLiteralSeparator:
|
||||||
|
Binary: 0
|
||||||
|
BinaryMinDigits: 0
|
||||||
|
Decimal: 0
|
||||||
|
DecimalMinDigits: 0
|
||||||
|
Hex: 0
|
||||||
|
HexMinDigits: 0
|
||||||
|
JavaScriptQuotes: Leave
|
||||||
|
JavaScriptWrapImports: true
|
||||||
|
KeepEmptyLinesAtTheStartOfBlocks: true
|
||||||
|
KeepEmptyLinesAtEOF: false
|
||||||
|
LambdaBodyIndentation: Signature
|
||||||
|
LineEnding: DeriveLF
|
||||||
|
MacroBlockBegin: ''
|
||||||
|
MacroBlockEnd: ''
|
||||||
|
MaxEmptyLinesToKeep: 1
|
||||||
|
NamespaceIndentation: None
|
||||||
|
ObjCBinPackProtocolList: Auto
|
||||||
|
ObjCBlockIndentWidth: 2
|
||||||
|
ObjCBreakBeforeNestedBlockParam: true
|
||||||
|
ObjCSpaceAfterProperty: false
|
||||||
|
ObjCSpaceBeforeProtocolList: true
|
||||||
|
PackConstructorInitializers: BinPack
|
||||||
|
PenaltyBreakAssignment: 2
|
||||||
|
PenaltyBreakBeforeFirstCallParameter: 19
|
||||||
|
PenaltyBreakComment: 300
|
||||||
|
PenaltyBreakFirstLessLess: 120
|
||||||
|
PenaltyBreakOpenParenthesis: 0
|
||||||
|
PenaltyBreakScopeResolution: 500
|
||||||
|
PenaltyBreakString: 1000
|
||||||
|
PenaltyBreakTemplateDeclaration: 10
|
||||||
|
PenaltyExcessCharacter: 1000000
|
||||||
|
PenaltyIndentedWhitespace: 0
|
||||||
|
PenaltyReturnTypeOnItsOwnLine: 60
|
||||||
|
PointerAlignment: Right
|
||||||
|
PPIndentWidth: -1
|
||||||
|
QualifierAlignment: Leave
|
||||||
|
ReferenceAlignment: Pointer
|
||||||
|
ReflowComments: true
|
||||||
|
RemoveBracesLLVM: false
|
||||||
|
RemoveParentheses: Leave
|
||||||
|
RemoveSemicolon: false
|
||||||
|
RequiresClausePosition: OwnLine
|
||||||
|
RequiresExpressionIndentation: OuterScope
|
||||||
|
SeparateDefinitionBlocks: Leave
|
||||||
|
ShortNamespaceLines: 1
|
||||||
|
SkipMacroDefinitionBody: false
|
||||||
|
SortIncludes: CaseSensitive
|
||||||
|
SortJavaStaticImport: Before
|
||||||
|
SortUsingDeclarations: LexicographicNumeric
|
||||||
|
SpaceAfterCStyleCast: false
|
||||||
|
SpaceAfterLogicalNot: false
|
||||||
|
SpaceAfterTemplateKeyword: true
|
||||||
|
SpaceAroundPointerQualifiers: Default
|
||||||
|
SpaceBeforeAssignmentOperators: true
|
||||||
|
SpaceBeforeCaseColon: false
|
||||||
|
SpaceBeforeCpp11BracedList: false
|
||||||
|
SpaceBeforeCtorInitializerColon: true
|
||||||
|
SpaceBeforeInheritanceColon: true
|
||||||
|
SpaceBeforeJsonColon: false
|
||||||
|
SpaceBeforeParens: ControlStatements
|
||||||
|
SpaceBeforeParensOptions:
|
||||||
|
AfterControlStatements: true
|
||||||
|
AfterForeachMacros: true
|
||||||
|
AfterFunctionDefinitionName: false
|
||||||
|
AfterFunctionDeclarationName: false
|
||||||
|
AfterIfMacros: true
|
||||||
|
AfterOverloadedOperator: false
|
||||||
|
AfterPlacementOperator: true
|
||||||
|
AfterRequiresInClause: false
|
||||||
|
AfterRequiresInExpression: false
|
||||||
|
BeforeNonEmptyParentheses: false
|
||||||
|
SpaceBeforeRangeBasedForLoopColon: true
|
||||||
|
SpaceBeforeSquareBrackets: false
|
||||||
|
SpaceInEmptyBlock: false
|
||||||
|
SpacesBeforeTrailingComments: 1
|
||||||
|
SpacesInAngles: Never
|
||||||
|
SpacesInContainerLiterals: true
|
||||||
|
SpacesInLineCommentPrefix:
|
||||||
|
Minimum: 1
|
||||||
|
Maximum: -1
|
||||||
|
SpacesInParens: Never
|
||||||
|
SpacesInParensOptions:
|
||||||
|
InCStyleCasts: false
|
||||||
|
InConditionalStatements: false
|
||||||
|
InEmptyParentheses: false
|
||||||
|
Other: false
|
||||||
|
SpacesInSquareBrackets: false
|
||||||
|
Standard: Latest
|
||||||
|
StatementAttributeLikeMacros:
|
||||||
|
- Q_EMIT
|
||||||
|
StatementMacros:
|
||||||
|
- Q_UNUSED
|
||||||
|
- QT_REQUIRE_VERSION
|
||||||
|
TabWidth: 8
|
||||||
|
UseTab: Never
|
||||||
|
VerilogBreakBetweenInstancePorts: true
|
||||||
|
WhitespaceSensitiveMacros:
|
||||||
|
- BOOST_PP_STRINGIZE
|
||||||
|
- CF_SWIFT_NAME
|
||||||
|
- NS_SWIFT_NAME
|
||||||
|
- PP_STRINGIZE
|
||||||
|
- STRINGIZE
|
||||||
|
...
|
||||||
|
|
990
sinqEPICSApp/src/newPmacV3Axis.cpp
Normal file
990
sinqEPICSApp/src/newPmacV3Axis.cpp
Normal file
@ -0,0 +1,990 @@
|
|||||||
|
#include "newPmacV3Axis.h"
|
||||||
|
#include "asynOctetSyncIO.h"
|
||||||
|
#include "newPmacV3Controller.h"
|
||||||
|
#include <cmath>
|
||||||
|
#include <errlog.h>
|
||||||
|
#include <limits>
|
||||||
|
#include <math.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
newPmacV3Axis::newPmacV3Axis(newPmacV3Controller *pC, int axisNo)
|
||||||
|
: asynMotorAxis(pC, axisNo), pC_(pC) {
|
||||||
|
|
||||||
|
static const char *functionName = "newPmacV3Axis::newPmacV3Axis";
|
||||||
|
asynStatus status = asynSuccess;
|
||||||
|
|
||||||
|
/*
|
||||||
|
The superclass constructor SINQAxis calls in turn its superclass constructor
|
||||||
|
asynMotorAxis. In the latter, a pointer to the constructed object this is
|
||||||
|
stored inside the array pAxes_:
|
||||||
|
|
||||||
|
pC->pAxes_[axisNo] = this;
|
||||||
|
|
||||||
|
Therefore, the axes are managed by the controller pC. See C804Controller.cpp
|
||||||
|
for further explanation. If axisNo is out of bounds, asynMotorAxis prints an
|
||||||
|
error (see
|
||||||
|
https://github.com/epics-modules/motor/blob/master/motorApp/MotorSrc/asynMotorAxis.cpp,
|
||||||
|
line 40). However, we want the IOC creation to stop completely, since this
|
||||||
|
is a configuration error.
|
||||||
|
*/
|
||||||
|
if (axisNo >= pC->numAxes_) {
|
||||||
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
|
"%s: FATAL ERROR: Axis index %d must be smaller than the "
|
||||||
|
"total number of axes %d. Terminating IOC.",
|
||||||
|
functionName, axisNo_, pC->numAxes_);
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize all member variables
|
||||||
|
initial_poll_ = true;
|
||||||
|
waitForHandshake_ = false;
|
||||||
|
|
||||||
|
// Wait 10 seconds for the handshake until declaring a timeout
|
||||||
|
handshakeTimeout_ = 10;
|
||||||
|
|
||||||
|
// Placeholder, is overwritten later
|
||||||
|
time_at_init_poll_ = 0;
|
||||||
|
|
||||||
|
// After 3 idle polls, the parameter library definitely had enough time to
|
||||||
|
// be initialized
|
||||||
|
timeout_param_lib_init_ = 3 * pC->idlePollPeriod_;
|
||||||
|
|
||||||
|
// Provide initial values for some parameter library entries
|
||||||
|
status = pC_->setIntegerParam(axisNo_, pC_->rereadEncoderPosition_, 0);
|
||||||
|
if (status != asynSuccess) {
|
||||||
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
|
"%s: FATAL ERROR: Setting an initial parameter library value "
|
||||||
|
"for rereadEncoderPosition_ in axis %d. Terminating IOC.",
|
||||||
|
functionName, axisNo_);
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
status = pC_->setDoubleParam(axisNo_, pC_->motorPosition_, 0.0);
|
||||||
|
if (status != asynSuccess) {
|
||||||
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
|
"%s: FATAL ERROR: Setting an initial parameter library value "
|
||||||
|
"for motorPosition_ in axis %d. Terminating IOC.",
|
||||||
|
functionName, axisNo_);
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This value is updated in the poll. Initially, we assume that the motor is
|
||||||
|
// not enabled.
|
||||||
|
status = pC_->setIntegerParam(axisNo_, pC_->motorEnabled_, 0);
|
||||||
|
if (status != asynSuccess) {
|
||||||
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
|
"%s: FATAL ERROR: Setting an initial parameter library value "
|
||||||
|
"for motorPosition_ in axis %d. Terminating IOC.",
|
||||||
|
functionName, axisNo_);
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newPmacV3Axis::~newPmacV3Axis(void) {
|
||||||
|
// Since the controller memory is managed somewhere else, we don't need to
|
||||||
|
// clean up the pointer pC here.
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Read the configuration from the motor control unit and the parameter library.
|
||||||
|
This operation is only allowed if the motor is not moving
|
||||||
|
*/
|
||||||
|
asynStatus newPmacV3Axis::readConfig() {
|
||||||
|
|
||||||
|
// Local variable declaration
|
||||||
|
static const char *functionName = "newPmacV3Axis::readConfig";
|
||||||
|
asynStatus status = asynSuccess;
|
||||||
|
char command[pC_->MAXBUF_], response[pC_->MAXBUF_];
|
||||||
|
int nvals = 0;
|
||||||
|
double highLimit = 0.0;
|
||||||
|
double lowLimit = 0.0;
|
||||||
|
double motorRecResolution = 0.0;
|
||||||
|
double position = 0.0;
|
||||||
|
int axStatus = 0;
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
// Motor resolution from parameter library
|
||||||
|
status = pC_->getDoubleParam(axisNo_, pC_->motorRecResolution_,
|
||||||
|
&motorRecResolution);
|
||||||
|
if (status == asynParamUndefined) {
|
||||||
|
return asynParamUndefined;
|
||||||
|
} else if (status != asynSuccess) {
|
||||||
|
return pC_->paramLibAccessFailed(status, functionName,
|
||||||
|
"motorRecResolution_");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Software limits and current position
|
||||||
|
snprintf(command, sizeof(command), "P%2.2d00 Q%2.2d10 Q%2.2d13 Q%2.2d14",
|
||||||
|
axisNo_, axisNo_, axisNo_, axisNo_);
|
||||||
|
status = pC_->writeRead(axisNo_, command, response, true);
|
||||||
|
nvals = sscanf(response, "%d %lf %lf %lf", &axStatus, &position, &highLimit,
|
||||||
|
&lowLimit);
|
||||||
|
if (pC_->checkNumExpectedReads(4, nvals, functionName, command, response,
|
||||||
|
axisNo_) != asynSuccess) {
|
||||||
|
return asynError;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the motor is not in idle status, do not read the configuration
|
||||||
|
if (axStatus != 0) {
|
||||||
|
return asynError;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform from motor to user coordinates
|
||||||
|
position = position * motorRecResolution;
|
||||||
|
highLimit = highLimit * motorRecResolution;
|
||||||
|
lowLimit = lowLimit * motorRecResolution;
|
||||||
|
|
||||||
|
/*
|
||||||
|
The axis limits are set as: ({[]})
|
||||||
|
where [] are the positive and negative limits set in EPICS/NICOS, {} are the
|
||||||
|
software limits set on the MCU and () are the hardware limit switches. In
|
||||||
|
other words, the EPICS/NICOS limits must be stricter than the software
|
||||||
|
limits on the MCU which in turn should be stricter than the hardware limit
|
||||||
|
switches. For example, if the hardware limit switches are at [-10, 10], the
|
||||||
|
software limits could be at [-9, 9] and the EPICS / NICOS limits could be at
|
||||||
|
[-8, 8]. Therefore, we cannot use the software limits read from the MCU
|
||||||
|
directly, but need to shrink them a bit. In this case, we're shrinking them
|
||||||
|
by 0.1 mm or 0.1 degree (depending on the axis type) on both sides.
|
||||||
|
*/
|
||||||
|
highLimit = highLimit - 0.1;
|
||||||
|
lowLimit = lowLimit + 0.1;
|
||||||
|
|
||||||
|
// Store these values in the parameter library
|
||||||
|
status = pC_->setDoubleParam(axisNo_, pC_->motorPosition_, position);
|
||||||
|
if (status != asynSuccess) {
|
||||||
|
return pC_->paramLibAccessFailed(status, functionName,
|
||||||
|
"motorPosition_");
|
||||||
|
}
|
||||||
|
status = pC_->setDoubleParam(axisNo_, pC_->motorLowLimit_, lowLimit);
|
||||||
|
if (status != asynSuccess) {
|
||||||
|
return pC_->paramLibAccessFailed(status, functionName,
|
||||||
|
"motorLowLimit_");
|
||||||
|
}
|
||||||
|
status = pC_->setDoubleParam(axisNo_, pC_->motorHighLimit_, highLimit);
|
||||||
|
if (status != asynSuccess) {
|
||||||
|
return pC_->paramLibAccessFailed(status, functionName,
|
||||||
|
"motorHighLimit_");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
|
"%s: Updating the parameter library failed for axis %d\n",
|
||||||
|
functionName, axisNo_);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this->readEncoderType();
|
||||||
|
}
|
||||||
|
|
||||||
|
asynStatus newPmacV3Axis::poll(bool *moving) {
|
||||||
|
// Local variable declaration
|
||||||
|
static const char *functionName = "newPmacV3Axis::poll";
|
||||||
|
asynStatus pl_status = asynSuccess;
|
||||||
|
asynStatus poll_status = asynSuccess;
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
// If this poll is the initial poll, check if the parameter library has
|
||||||
|
// already been initialized. If not, force EPCIS to repeat the poll until
|
||||||
|
// the initialization is complete (or until a timeout is reached). Once the
|
||||||
|
// parameter library has been initialized, read configuration data from the
|
||||||
|
// motor controller into it.
|
||||||
|
if (initial_poll_) {
|
||||||
|
|
||||||
|
if (time_at_init_poll_ == 0) {
|
||||||
|
time_at_init_poll_ = time(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (time(NULL) > (time_at_init_poll_ + timeout_param_lib_init_)) {
|
||||||
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
|
"%s: FATAL ERROR: Could not initialize the parameter "
|
||||||
|
"library until the timeout of %ld seconds after IOC "
|
||||||
|
"startup. Terminating IOC.",
|
||||||
|
functionName, timeout_param_lib_init_);
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
poll_status = readConfig();
|
||||||
|
if (poll_status == asynSuccess) {
|
||||||
|
initial_poll_ = false;
|
||||||
|
} else if (poll_status == asynParamUndefined) {
|
||||||
|
// Wait for 100 ms until trying the entire poll again
|
||||||
|
usleep(100000);
|
||||||
|
return poll_status;
|
||||||
|
} else {
|
||||||
|
// Something else went completly wrong => Abort the program
|
||||||
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
|
"%s: FATAL ERROR: Reading a value from the parameter "
|
||||||
|
"library failed for axis %d (%s). Terminating",
|
||||||
|
functionName, axisNo_,
|
||||||
|
pC_->stringifyAsynStatus(poll_status));
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The poll function is just a wrapper around pollNoUpdate and
|
||||||
|
// handles mainly the callParamCallbacks() function. This wrapper is used
|
||||||
|
// to make sure callParamCallbacks() is called in case of a premature
|
||||||
|
// return.
|
||||||
|
poll_status = newPmacV3Axis::pollNoUpdate(moving);
|
||||||
|
|
||||||
|
// If the poll status is ok, reset the error indicators in the parameter
|
||||||
|
// library
|
||||||
|
if (poll_status == asynSuccess) {
|
||||||
|
pl_status = setIntegerParam(pC_->motorStatusProblem_, false);
|
||||||
|
if (pl_status != asynSuccess) {
|
||||||
|
pC_->paramLibAccessFailed(pl_status, functionName,
|
||||||
|
"motorStatusProblem_");
|
||||||
|
}
|
||||||
|
pl_status = setIntegerParam(pC_->motorStatusCommsError_, false);
|
||||||
|
if (pl_status != asynSuccess) {
|
||||||
|
pC_->paramLibAccessFailed(pl_status, functionName,
|
||||||
|
"motorStatusCommsError_");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// According to the function documentation of asynMotorAxis::poll, this
|
||||||
|
// function should be called at the end of a poll implementation.
|
||||||
|
pl_status = callParamCallbacks();
|
||||||
|
if (pl_status != asynSuccess) {
|
||||||
|
// If we can't communicate with the parameter library, it doesn't make
|
||||||
|
// sense to try and upstream this to the user -> Just log the error
|
||||||
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
|
"%s: Updating the parameter library failed for axis %d\n",
|
||||||
|
functionName, axisNo_);
|
||||||
|
poll_status = pl_status;
|
||||||
|
}
|
||||||
|
|
||||||
|
return poll_status;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform the actual poll
|
||||||
|
asynStatus newPmacV3Axis::pollNoUpdate(bool *moving) {
|
||||||
|
|
||||||
|
// Return value for the poll
|
||||||
|
asynStatus poll_status = asynSuccess;
|
||||||
|
|
||||||
|
// Status of read-write-operations of ASCII commands to the controller
|
||||||
|
asynStatus rw_status = asynSuccess;
|
||||||
|
|
||||||
|
// Status of parameter library operations
|
||||||
|
asynStatus pl_status = asynSuccess;
|
||||||
|
|
||||||
|
static const char *functionName = "newPmacV3Axis::poll";
|
||||||
|
char command[pC_->MAXBUF_], response[pC_->MAXBUF_];
|
||||||
|
int nvals = 0;
|
||||||
|
|
||||||
|
int direction = 0;
|
||||||
|
int error = 0;
|
||||||
|
int axStatus = 0;
|
||||||
|
double currentPosition = 0.0;
|
||||||
|
double previousPosition = 0.0;
|
||||||
|
double motorRecResolution = 0.0;
|
||||||
|
int handshakePerformed = 0;
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
// Are we currently waiting for a handshake?
|
||||||
|
if (waitForHandshake_) {
|
||||||
|
snprintf(command, sizeof(command), "P%2.2d23", axisNo_);
|
||||||
|
rw_status = pC_->writeRead(axisNo_, command, response, true);
|
||||||
|
if (rw_status != asynSuccess) {
|
||||||
|
return rw_status;
|
||||||
|
}
|
||||||
|
|
||||||
|
nvals = sscanf(response, "%d", &handshakePerformed);
|
||||||
|
if (pC_->checkNumExpectedReads(1, nvals, functionName, command,
|
||||||
|
response, axisNo_) != asynSuccess) {
|
||||||
|
return asynError;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handshakePerformed == 1) {
|
||||||
|
// Handshake has been performed successfully -> Continue with the
|
||||||
|
// poll
|
||||||
|
waitForHandshake_ = false;
|
||||||
|
} else {
|
||||||
|
// Still waiting for the handshake. This is already part of the
|
||||||
|
// movement procedure!
|
||||||
|
if (time(NULL) < timeAtHandshake_ + handshakeTimeout_) {
|
||||||
|
*moving = true;
|
||||||
|
return asynSuccess;
|
||||||
|
} else {
|
||||||
|
// Timed out when waiting for the handshake
|
||||||
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
|
"%s: Axis %d timed out when waiting for the "
|
||||||
|
"handshake with the MCU.\n",
|
||||||
|
functionName, axisNo_);
|
||||||
|
pl_status = setStringParam(
|
||||||
|
pC_->messageText_, "Handshake timed out. This is a bug.");
|
||||||
|
if (pl_status != asynSuccess) {
|
||||||
|
return pC_->paramLibAccessFailed(pl_status, functionName,
|
||||||
|
"messageText_");
|
||||||
|
}
|
||||||
|
waitForHandshake_ = false;
|
||||||
|
return asynError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Motor resolution from parameter library
|
||||||
|
pl_status = pC_->getDoubleParam(axisNo_, pC_->motorRecResolution_,
|
||||||
|
&motorRecResolution);
|
||||||
|
if (pl_status != asynSuccess) {
|
||||||
|
return pC_->paramLibAccessFailed(pl_status, functionName,
|
||||||
|
"motorRecResolution_");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the previous motor position
|
||||||
|
pl_status =
|
||||||
|
pC_->getDoubleParam(axisNo_, pC_->motorPosition_, &previousPosition);
|
||||||
|
if (pl_status != asynSuccess) {
|
||||||
|
return pC_->paramLibAccessFailed(pl_status, functionName,
|
||||||
|
"motorPosition_");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the axis status (Pxx00) and the current motor position (Qxx10)
|
||||||
|
snprintf(command, sizeof(command), "P%2.2d00 Q%2.2d10 P%2.2d01", axisNo_,
|
||||||
|
axisNo_, axisNo_);
|
||||||
|
rw_status = pC_->writeRead(axisNo_, command, response, true);
|
||||||
|
if (rw_status != asynSuccess) {
|
||||||
|
return rw_status;
|
||||||
|
}
|
||||||
|
|
||||||
|
nvals = sscanf(response, "%d %lf %d", &axStatus, ¤tPosition, &error);
|
||||||
|
if (pC_->checkNumExpectedReads(3, nvals, functionName, command, response,
|
||||||
|
axisNo_) != asynSuccess) {
|
||||||
|
return asynError;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intepret the status
|
||||||
|
switch (axStatus) {
|
||||||
|
case -6:
|
||||||
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW,
|
||||||
|
"%s: Axis %d is stopping\n", functionName, axisNo_);
|
||||||
|
*moving = true;
|
||||||
|
break;
|
||||||
|
case -5:
|
||||||
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW,
|
||||||
|
"%s: Axis %d is deactivated\n", functionName, axisNo_);
|
||||||
|
*moving = false;
|
||||||
|
|
||||||
|
pl_status = setStringParam(pC_->messageText_, "Deactivated");
|
||||||
|
if (pl_status != asynSuccess) {
|
||||||
|
return pC_->paramLibAccessFailed(pl_status, functionName,
|
||||||
|
"messageText_");
|
||||||
|
}
|
||||||
|
|
||||||
|
// No further evaluation of the axis status is necessary
|
||||||
|
return asynSuccess;
|
||||||
|
case -4:
|
||||||
|
asynPrint(
|
||||||
|
pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
|
"%s: Emergency stop has been activated. All axes are stopped.\n",
|
||||||
|
functionName);
|
||||||
|
*moving = false;
|
||||||
|
|
||||||
|
pl_status = setStringParam(pC_->messageText_, "Emergency stop");
|
||||||
|
if (pl_status != asynSuccess) {
|
||||||
|
return pC_->paramLibAccessFailed(pl_status, functionName,
|
||||||
|
"messageText_");
|
||||||
|
}
|
||||||
|
|
||||||
|
// No further evaluation of the axis status is necessary
|
||||||
|
return asynSuccess;
|
||||||
|
case -3:
|
||||||
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW,
|
||||||
|
"%s: Axis %d is inhibited\n", functionName, axisNo_);
|
||||||
|
*moving = false;
|
||||||
|
|
||||||
|
pl_status = setStringParam(pC_->messageText_, "Disabled");
|
||||||
|
if (pl_status != asynSuccess) {
|
||||||
|
return pC_->paramLibAccessFailed(pl_status, functionName,
|
||||||
|
"messageText_");
|
||||||
|
}
|
||||||
|
|
||||||
|
// No further evaluation of the axis status is necessary
|
||||||
|
return asynSuccess;
|
||||||
|
case 0:
|
||||||
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW,
|
||||||
|
"%s: Axis %d is ready for movement\n", functionName, axisNo_);
|
||||||
|
*moving = false;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW,
|
||||||
|
"%s: Move order for %d acknowledged\n", functionName,
|
||||||
|
axisNo_);
|
||||||
|
*moving = true;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW,
|
||||||
|
"%s: Move order for %d is possible\n", functionName, axisNo_);
|
||||||
|
*moving = true;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW,
|
||||||
|
"%s: %d in Air Cushion Outout status\n", functionName,
|
||||||
|
axisNo_);
|
||||||
|
*moving = true;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW,
|
||||||
|
"%s: %d in Air Cushion Input status\n", functionName,
|
||||||
|
axisNo_);
|
||||||
|
*moving = true;
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW,
|
||||||
|
"%s: Axis %d is moving\n", functionName, axisNo_);
|
||||||
|
*moving = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
|
"%s: Reached unreachable state P%2.2d00 = %d.\n",
|
||||||
|
functionName, axisNo_, axStatus);
|
||||||
|
pl_status =
|
||||||
|
setStringParam(pC_->messageText_,
|
||||||
|
"Unreachable state has been reached. This is a bug");
|
||||||
|
if (pl_status != asynSuccess) {
|
||||||
|
return pC_->paramLibAccessFailed(pl_status, functionName,
|
||||||
|
"messageText_");
|
||||||
|
}
|
||||||
|
*moving = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*moving) {
|
||||||
|
// If the axis is moving, evaluate the movement direction
|
||||||
|
if ((currentPosition - previousPosition) > 0) {
|
||||||
|
direction = 1;
|
||||||
|
} else {
|
||||||
|
direction = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error handling
|
||||||
|
switch (error) {
|
||||||
|
case 0:
|
||||||
|
// No error
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
// EPICS should already prevent this issue in the first place,
|
||||||
|
// since it contains the user limits
|
||||||
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
|
"%s: Target position would exceed user limits in axis "
|
||||||
|
"%d. EPICS should prevent an out-of-bounds target "
|
||||||
|
"position, this is a bug.\n",
|
||||||
|
functionName, axisNo_);
|
||||||
|
|
||||||
|
pl_status =
|
||||||
|
setStringParam(pC_->messageText_,
|
||||||
|
"Target position would exceed software limits. This "
|
||||||
|
"is a bug.");
|
||||||
|
if (pl_status != asynSuccess) {
|
||||||
|
return pC_->paramLibAccessFailed(pl_status, functionName,
|
||||||
|
"messageText_");
|
||||||
|
}
|
||||||
|
|
||||||
|
poll_status = asynError;
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
// Command not possible
|
||||||
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
|
"%s: Axis %d is still moving, but received another move "
|
||||||
|
"command. EPICS should prevent this, this is a bug.\n",
|
||||||
|
functionName, axisNo_);
|
||||||
|
|
||||||
|
pl_status = setStringParam(pC_->messageText_,
|
||||||
|
"Axis received move command while it is "
|
||||||
|
"still moving. This is a bug.");
|
||||||
|
if (pl_status != asynSuccess) {
|
||||||
|
return pC_->paramLibAccessFailed(pl_status, functionName,
|
||||||
|
"messageText_");
|
||||||
|
}
|
||||||
|
|
||||||
|
poll_status = asynError;
|
||||||
|
break;
|
||||||
|
case 10:
|
||||||
|
/*
|
||||||
|
Software limits of the controller have been hit. Since the EPICS limits
|
||||||
|
are derived from the software limits and are a little bit smaller, this
|
||||||
|
error case can only happen if either the axis has an incremental encoder
|
||||||
|
which is not properly homed or if a bug occured.
|
||||||
|
*/
|
||||||
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_WARNING,
|
||||||
|
"%s: Axis %d hit the controller limits. Try homing the axis, "
|
||||||
|
"if that doesn't remove the error, this is a bug.\n",
|
||||||
|
functionName, axisNo_);
|
||||||
|
|
||||||
|
snprintf(command, sizeof(command),
|
||||||
|
"Software limits hit (P%2.2d01 = %d). Try homing the motor. "
|
||||||
|
"If that doesn't work, contact the support",
|
||||||
|
axisNo_, error);
|
||||||
|
pl_status = setStringParam(pC_->messageText_, command);
|
||||||
|
if (pl_status != asynSuccess) {
|
||||||
|
return pC_->paramLibAccessFailed(pl_status, functionName,
|
||||||
|
"messageText_");
|
||||||
|
}
|
||||||
|
|
||||||
|
poll_status = asynError;
|
||||||
|
break;
|
||||||
|
case 11:
|
||||||
|
// Following error
|
||||||
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
|
"%s: Maximum allowed following error exceeded for axis %d.\n",
|
||||||
|
functionName, axisNo_);
|
||||||
|
|
||||||
|
snprintf(command, sizeof(command),
|
||||||
|
"Maximum allowed following error exceeded (P%2.2d01 = 11). "
|
||||||
|
"Please contact Electronics support.",
|
||||||
|
axisNo_);
|
||||||
|
pl_status = setStringParam(pC_->messageText_, command);
|
||||||
|
if (pl_status != asynSuccess) {
|
||||||
|
return pC_->paramLibAccessFailed(pl_status, functionName,
|
||||||
|
"messageText_");
|
||||||
|
}
|
||||||
|
|
||||||
|
poll_status = asynError;
|
||||||
|
break;
|
||||||
|
case 13:
|
||||||
|
// Watchdog of the controller final stage triggered
|
||||||
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
|
"%s: Driver hardware error triggered for axis %d.\n",
|
||||||
|
functionName, axisNo_);
|
||||||
|
|
||||||
|
snprintf(command, sizeof(command),
|
||||||
|
"Driver hardware error (P%2.2d01 = 13). "
|
||||||
|
"Please contact Electronics support.",
|
||||||
|
axisNo_);
|
||||||
|
pl_status = setStringParam(pC_->messageText_, command);
|
||||||
|
if (pl_status != asynSuccess) {
|
||||||
|
return pC_->paramLibAccessFailed(pl_status, functionName,
|
||||||
|
"messageText_");
|
||||||
|
}
|
||||||
|
|
||||||
|
poll_status = asynError;
|
||||||
|
break;
|
||||||
|
case 14:
|
||||||
|
// EPICS should already prevent this issue in the first place,
|
||||||
|
// since it contains the user limits
|
||||||
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
|
"%s: Target position would exceed hardware limits in axis "
|
||||||
|
"%d. Homing might be necessary.\n",
|
||||||
|
functionName, axisNo_);
|
||||||
|
|
||||||
|
snprintf(command, sizeof(command),
|
||||||
|
"Move command exceeds hardware limits (P%2.2d01 = %d). Try "
|
||||||
|
"homing the motor. If that doesn't work, contact the support",
|
||||||
|
axisNo_, error);
|
||||||
|
pl_status = setStringParam(pC_->messageText_, command);
|
||||||
|
if (pl_status != asynSuccess) {
|
||||||
|
return pC_->paramLibAccessFailed(pl_status, functionName,
|
||||||
|
"messageText_");
|
||||||
|
}
|
||||||
|
|
||||||
|
poll_status = asynError;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
|
"%s: Axis %d reached an unreachable state (P%2.2d01 = %d).\n",
|
||||||
|
functionName, axisNo_, axisNo_, error);
|
||||||
|
|
||||||
|
pl_status = setStringParam(pC_->messageText_,
|
||||||
|
"Axis reached an unreachable state. Please "
|
||||||
|
"contact electronics support");
|
||||||
|
if (pl_status != asynSuccess) {
|
||||||
|
return pC_->paramLibAccessFailed(pl_status, functionName,
|
||||||
|
"messageText_");
|
||||||
|
}
|
||||||
|
|
||||||
|
poll_status = asynError;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the parameter library
|
||||||
|
if (error != 0) {
|
||||||
|
pl_status = setIntegerParam(pC_->motorStatusProblem_, true);
|
||||||
|
if (pl_status != asynSuccess) {
|
||||||
|
return pC_->paramLibAccessFailed(pl_status, functionName,
|
||||||
|
"motorStatusProblem_");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*moving == false) {
|
||||||
|
pl_status = setIntegerParam(pC_->motorMoveToHome_, 0);
|
||||||
|
if (pl_status != asynSuccess) {
|
||||||
|
return pC_->paramLibAccessFailed(pl_status, functionName,
|
||||||
|
"motorMoveToHome_");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pl_status =
|
||||||
|
setIntegerParam(pC_->motorEnabled_, (axStatus != -3 && axStatus != -5));
|
||||||
|
if (pl_status != asynSuccess) {
|
||||||
|
return pC_->paramLibAccessFailed(pl_status, functionName,
|
||||||
|
"motorEnabled_");
|
||||||
|
}
|
||||||
|
|
||||||
|
pl_status = setIntegerParam(pC_->motorStatusMoving_, *moving);
|
||||||
|
if (pl_status != asynSuccess) {
|
||||||
|
return pC_->paramLibAccessFailed(pl_status, functionName,
|
||||||
|
"motorStatusMoving_");
|
||||||
|
}
|
||||||
|
|
||||||
|
pl_status = setIntegerParam(pC_->motorStatusDone_, !(*moving));
|
||||||
|
if (pl_status != asynSuccess) {
|
||||||
|
return pC_->paramLibAccessFailed(pl_status, functionName,
|
||||||
|
"motorStatusDone_");
|
||||||
|
}
|
||||||
|
|
||||||
|
pl_status = setIntegerParam(pC_->motorStatusDirection_, direction);
|
||||||
|
if (pl_status != asynSuccess) {
|
||||||
|
return pC_->paramLibAccessFailed(pl_status, functionName,
|
||||||
|
"motorStatusDirection_");
|
||||||
|
}
|
||||||
|
|
||||||
|
pl_status = setDoubleParam(pC_->motorPosition_, currentPosition);
|
||||||
|
if (pl_status != asynSuccess) {
|
||||||
|
return pC_->paramLibAccessFailed(pl_status, functionName,
|
||||||
|
"motorPosition_");
|
||||||
|
}
|
||||||
|
|
||||||
|
return poll_status;
|
||||||
|
}
|
||||||
|
|
||||||
|
asynStatus newPmacV3Axis::move(double position, int relative,
|
||||||
|
double minVelocity, double maxVelocity,
|
||||||
|
double acceleration) {
|
||||||
|
|
||||||
|
// Status of read-write-operations of ASCII commands to the controller
|
||||||
|
asynStatus rw_status = asynSuccess;
|
||||||
|
|
||||||
|
// Status of parameter library operations
|
||||||
|
asynStatus pl_status = asynSuccess;
|
||||||
|
|
||||||
|
static const char *functionName = "newPmacV3Axis::move";
|
||||||
|
char command[pC_->MAXBUF_], response[pC_->MAXBUF_];
|
||||||
|
double motorCoordinatesPosition = 0.0;
|
||||||
|
int enabled = 0;
|
||||||
|
double motorRecResolution = 0.0;
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
pl_status = pC_->getIntegerParam(axisNo_, pC_->motorEnabled_, &enabled);
|
||||||
|
if (pl_status != asynSuccess) {
|
||||||
|
return pC_->paramLibAccessFailed(pl_status, functionName,
|
||||||
|
"motorEnabled_");
|
||||||
|
}
|
||||||
|
|
||||||
|
pl_status = pC_->getDoubleParam(axisNo_, pC_->motorRecResolution_,
|
||||||
|
&motorRecResolution);
|
||||||
|
if (pl_status != asynSuccess) {
|
||||||
|
return pC_->paramLibAccessFailed(pl_status, functionName,
|
||||||
|
"motorRecResolution_");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enabled == 0) {
|
||||||
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
|
"%s: Axis %d is disabled\n", functionName, axisNo_);
|
||||||
|
return asynSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert from user to motor units
|
||||||
|
motorCoordinatesPosition = position / motorRecResolution;
|
||||||
|
|
||||||
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW,
|
||||||
|
"%s: Start of axis %d to position %lf\n", functionName, axisNo_,
|
||||||
|
position);
|
||||||
|
|
||||||
|
// Perform handshake, Set target position and start the move command
|
||||||
|
|
||||||
|
if (relative) {
|
||||||
|
snprintf(command, sizeof(command), "P%2.2d23=0 Q%2.2d02=%lf M%2.2d=2",
|
||||||
|
axisNo_, axisNo_, motorCoordinatesPosition, axisNo_);
|
||||||
|
} else {
|
||||||
|
snprintf(command, sizeof(command), "P%2.2d23=0 Q%2.2d01=%lf M%2.2d=1",
|
||||||
|
axisNo_, axisNo_, motorCoordinatesPosition, axisNo_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't expect an answer
|
||||||
|
rw_status = pC_->writeRead(axisNo_, command, response, false);
|
||||||
|
if (rw_status != asynSuccess) {
|
||||||
|
asynPrint(
|
||||||
|
pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
|
"%s: Starting movement to target position %lf failed for axis %d\n",
|
||||||
|
functionName, position, axisNo_);
|
||||||
|
pl_status = setIntegerParam(pC_->motorStatusProblem_, true);
|
||||||
|
if (pl_status != asynSuccess) {
|
||||||
|
return pC_->paramLibAccessFailed(pl_status, functionName,
|
||||||
|
"motorStatusProblem_");
|
||||||
|
}
|
||||||
|
return rw_status;
|
||||||
|
}
|
||||||
|
|
||||||
|
// In the next poll, we will check if the handshake has been performed in a
|
||||||
|
// reasonable time
|
||||||
|
waitForHandshake_ = true;
|
||||||
|
timeAtHandshake_ = time(NULL);
|
||||||
|
|
||||||
|
return rw_status;
|
||||||
|
}
|
||||||
|
|
||||||
|
asynStatus newPmacV3Axis::stop(double acceleration) {
|
||||||
|
|
||||||
|
// Status of read-write-operations of ASCII commands to the controller
|
||||||
|
asynStatus rw_status = asynSuccess;
|
||||||
|
|
||||||
|
// Status of parameter library operations
|
||||||
|
asynStatus pl_status = asynSuccess;
|
||||||
|
|
||||||
|
static const char *functionName = "newPmacV3Axis::stopAxis";
|
||||||
|
bool moving = false;
|
||||||
|
char command[pC_->MAXBUF_], response[pC_->MAXBUF_];
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
pl_status = pollNoUpdate(&moving);
|
||||||
|
if (pl_status != asynSuccess) {
|
||||||
|
return pl_status;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (moving) {
|
||||||
|
// only send a stop when actually moving
|
||||||
|
snprintf(command, sizeof(command), "M%2.2d=8", axisNo_);
|
||||||
|
rw_status = pC_->writeRead(axisNo_, command, response, false);
|
||||||
|
|
||||||
|
if (rw_status != asynSuccess) {
|
||||||
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
|
"%s: Stopping the movement failed for axis %d\n",
|
||||||
|
functionName, axisNo_);
|
||||||
|
|
||||||
|
pl_status = setIntegerParam(pC_->motorStatusProblem_, true);
|
||||||
|
if (pl_status != asynSuccess) {
|
||||||
|
return pC_->paramLibAccessFailed(pl_status, functionName,
|
||||||
|
"motorStatusProblem_");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rw_status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Home the axis. On absolute encoder systems, this is a no-op
|
||||||
|
*/
|
||||||
|
asynStatus newPmacV3Axis::home(double min_velocity, double max_velocity,
|
||||||
|
double acceleration, int forwards) {
|
||||||
|
|
||||||
|
// Status of read-write-operations of ASCII commands to the controller
|
||||||
|
asynStatus rw_status = asynSuccess;
|
||||||
|
|
||||||
|
// Status of parameter library operations
|
||||||
|
asynStatus pl_status = asynSuccess;
|
||||||
|
|
||||||
|
char command[pC_->MAXBUF_], response[pC_->MAXBUF_];
|
||||||
|
static const char *functionName = "newPmacV3Axis::home";
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
pl_status =
|
||||||
|
pC_->getStringParam(axisNo_, pC_->encoderType_, pC_->MAXBUF_, response);
|
||||||
|
if (pl_status != asynSuccess) {
|
||||||
|
return pC_->paramLibAccessFailed(pl_status, functionName,
|
||||||
|
"encoderType_");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only send the home command if the axis has an incremental encoder
|
||||||
|
if (strcmp(response, IncrementalEncoder) == 0) {
|
||||||
|
snprintf(command, sizeof(command), "M%2.2d=9", axisNo_);
|
||||||
|
rw_status = pC_->writeRead(axisNo_, command, response, false);
|
||||||
|
if (rw_status != asynSuccess) {
|
||||||
|
return rw_status;
|
||||||
|
}
|
||||||
|
|
||||||
|
pl_status = setIntegerParam(pC_->motorMoveToHome_, 1);
|
||||||
|
if (pl_status != asynSuccess) {
|
||||||
|
return pC_->paramLibAccessFailed(pl_status, functionName,
|
||||||
|
"motorMoveToHome_");
|
||||||
|
}
|
||||||
|
|
||||||
|
pl_status = setStringParam(pC_->messageText_, "Homing");
|
||||||
|
if (pl_status != asynSuccess) {
|
||||||
|
return pC_->paramLibAccessFailed(pl_status, functionName,
|
||||||
|
"messageText_");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rw_status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Read the encoder type and update the parameter library accordingly
|
||||||
|
*/
|
||||||
|
asynStatus newPmacV3Axis::readEncoderType() {
|
||||||
|
|
||||||
|
// Status of read-write-operations of ASCII commands to the controller
|
||||||
|
asynStatus rw_status = asynSuccess;
|
||||||
|
|
||||||
|
// Status of parameter library operations
|
||||||
|
asynStatus pl_status = asynSuccess;
|
||||||
|
|
||||||
|
char command[pC_->MAXBUF_], response[pC_->MAXBUF_];
|
||||||
|
static const char *functionName = "newPmacV3Axis::readEncoderType";
|
||||||
|
int nvals = 0;
|
||||||
|
int encoder_id = 0;
|
||||||
|
char encoderType[pC_->MAXBUF_];
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
// Check if this is an absolute encoder
|
||||||
|
snprintf(command, sizeof(command), "I%2.2X04", axisNo_);
|
||||||
|
rw_status = pC_->writeRead(axisNo_, command, response, true);
|
||||||
|
if (rw_status != asynSuccess) {
|
||||||
|
return rw_status;
|
||||||
|
}
|
||||||
|
|
||||||
|
int reponse_length = strlen(response);
|
||||||
|
if (reponse_length < 3) {
|
||||||
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
|
"%s: Unexpected reponse '%s' from axis %d on "
|
||||||
|
"controller %s while reading the encoder type. Aborting...\n",
|
||||||
|
functionName, response, axisNo_, pC_->portName);
|
||||||
|
return asynError;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We are only interested in the last two digits and the last value in
|
||||||
|
// the string before the terminator is \r
|
||||||
|
nvals = sscanf(response + (reponse_length - 3), "%2X", &encoder_id);
|
||||||
|
if (pC_->checkNumExpectedReads(1, nvals, functionName, command, response,
|
||||||
|
axisNo_) != asynSuccess) {
|
||||||
|
return asynError;
|
||||||
|
}
|
||||||
|
|
||||||
|
snprintf(command, sizeof(command), "P46");
|
||||||
|
rw_status = pC_->writeRead(axisNo_, command, response, true);
|
||||||
|
if (rw_status != asynSuccess) {
|
||||||
|
return rw_status;
|
||||||
|
}
|
||||||
|
int number_of_axes = strtol(response, NULL, 10);
|
||||||
|
|
||||||
|
// If true, the encoder is incremental
|
||||||
|
if (encoder_id <= number_of_axes) {
|
||||||
|
pl_status = setStringParam(pC_->encoderType_, IncrementalEncoder);
|
||||||
|
} else {
|
||||||
|
pl_status = setStringParam(pC_->encoderType_, AbsoluteEncoder);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pl_status != asynSuccess) {
|
||||||
|
return pC_->paramLibAccessFailed(pl_status, functionName,
|
||||||
|
"encoderType_");
|
||||||
|
}
|
||||||
|
return asynSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
asynStatus newPmacV3Axis::enable(int on) {
|
||||||
|
|
||||||
|
static const char *functionName = "newPmacV3Axis::enable";
|
||||||
|
int ax_status = 0;
|
||||||
|
int timeout_enable_disable = 2;
|
||||||
|
char command[pC_->MAXBUF_], response[pC_->MAXBUF_];
|
||||||
|
int nvals = 0;
|
||||||
|
|
||||||
|
// Status of read-write-operations of ASCII commands to the controller
|
||||||
|
asynStatus rw_status = asynSuccess;
|
||||||
|
|
||||||
|
// Status of parameter library operations
|
||||||
|
asynStatus pl_status = asynSuccess;
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
// Check if the axis is currently enabled
|
||||||
|
snprintf(command, sizeof(command), "P%2.2d00", axisNo_);
|
||||||
|
rw_status = pC_->writeRead(axisNo_, command, response, true);
|
||||||
|
if (rw_status != asynSuccess) {
|
||||||
|
return rw_status;
|
||||||
|
}
|
||||||
|
|
||||||
|
nvals = sscanf(response, "%d", &ax_status);
|
||||||
|
if (pC_->checkNumExpectedReads(1, nvals, functionName, command, response,
|
||||||
|
axisNo_) != asynSuccess) {
|
||||||
|
return asynError;
|
||||||
|
}
|
||||||
|
|
||||||
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW,
|
||||||
|
"%s: Axis status %d, new input %d \n", functionName, ax_status,
|
||||||
|
on);
|
||||||
|
|
||||||
|
// Axis is already enabled / disabled and a new enable / disable command
|
||||||
|
// was sent => Do nothing
|
||||||
|
if ((ax_status != -3) == on) {
|
||||||
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_WARNING,
|
||||||
|
"%s: axis %d on controller %s is already %s\n", functionName,
|
||||||
|
axisNo_, pC_->portName, on ? "enabled" : "disabled");
|
||||||
|
return asynSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable / disable the axis
|
||||||
|
snprintf(command, sizeof(command), "M%2.2d14=%d", axisNo_, on);
|
||||||
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW,
|
||||||
|
"%s: %s axis %d on controller %s\n", functionName,
|
||||||
|
on ? "Enable" : "Disable", axisNo_, pC_->portName);
|
||||||
|
if (on == 0) {
|
||||||
|
pl_status = setStringParam(pC_->messageText_, "Disabling ...");
|
||||||
|
} else {
|
||||||
|
pl_status = setStringParam(pC_->messageText_, "Enabling ...");
|
||||||
|
}
|
||||||
|
if (pl_status != asynSuccess) {
|
||||||
|
return pC_->paramLibAccessFailed(pl_status, functionName,
|
||||||
|
"messageText_");
|
||||||
|
}
|
||||||
|
rw_status = pC_->writeRead(axisNo_, command, response, false);
|
||||||
|
if (rw_status != asynSuccess) {
|
||||||
|
return rw_status;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query the axis status every few milliseconds until the axis has been
|
||||||
|
// enabled or until the timeout has been reached
|
||||||
|
snprintf(command, sizeof(command), "P%2.2d00", axisNo_);
|
||||||
|
int startTime = time(NULL);
|
||||||
|
while (time(NULL) < startTime + timeout_enable_disable) {
|
||||||
|
|
||||||
|
// Read the axis status
|
||||||
|
usleep(100000);
|
||||||
|
rw_status = pC_->writeRead(axisNo_, command, response, true);
|
||||||
|
if (rw_status != asynSuccess) {
|
||||||
|
return rw_status;
|
||||||
|
}
|
||||||
|
nvals = sscanf(response, "%d", &ax_status);
|
||||||
|
if (pC_->checkNumExpectedReads(1, nvals, functionName, command,
|
||||||
|
response, axisNo_) != asynSuccess) {
|
||||||
|
return asynError;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((ax_status != -3) == on) {
|
||||||
|
bool moving = false;
|
||||||
|
// Perform a poll to update the parameter library
|
||||||
|
poll(&moving);
|
||||||
|
return asynSuccess;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Failed to change axis status within timeout_enable_disable => Send a
|
||||||
|
// corresponding message
|
||||||
|
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW,
|
||||||
|
"%s: Failed to %s axis %d on controller %s within %d seconds\n",
|
||||||
|
functionName, on ? "enable" : "disable", axisNo_, pC_->portName,
|
||||||
|
timeout_enable_disable);
|
||||||
|
|
||||||
|
// Output message to user
|
||||||
|
snprintf(command, sizeof(command), "Failed to %s within %d seconds",
|
||||||
|
on ? "enable" : "disable", timeout_enable_disable);
|
||||||
|
pl_status = setStringParam(pC_->messageText_, "Enabling ...");
|
||||||
|
if (pl_status != asynSuccess) {
|
||||||
|
return pC_->paramLibAccessFailed(pl_status, functionName,
|
||||||
|
"messageText_");
|
||||||
|
}
|
||||||
|
return asynError;
|
||||||
|
}
|
41
sinqEPICSApp/src/newPmacV3Axis.h
Normal file
41
sinqEPICSApp/src/newPmacV3Axis.h
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
#ifndef pmacV3AXIS_H
|
||||||
|
#define pmacV3AXIS_H
|
||||||
|
#include "asynMotorAxis.h"
|
||||||
|
|
||||||
|
// Forward declaration of the controller class to resolve the cyclic dependency
|
||||||
|
// between C804Controller.h and C804Axis.h. See
|
||||||
|
// https://en.cppreference.com/w/cpp/language/class.
|
||||||
|
class newPmacV3Controller;
|
||||||
|
|
||||||
|
class newPmacV3Axis : public asynMotorAxis {
|
||||||
|
public:
|
||||||
|
/* These are the methods we override from the base class */
|
||||||
|
newPmacV3Axis(newPmacV3Controller *pController, int axisNo);
|
||||||
|
virtual ~newPmacV3Axis();
|
||||||
|
asynStatus move(double position, int relative, double min_velocity,
|
||||||
|
double max_velocity, double acceleration);
|
||||||
|
asynStatus stop(double acceleration);
|
||||||
|
asynStatus home(double minVelocity, double maxVelocity, double acceleration,
|
||||||
|
int forwards);
|
||||||
|
asynStatus poll(bool *moving);
|
||||||
|
asynStatus pollNoUpdate(bool *moving);
|
||||||
|
asynStatus enable(int on);
|
||||||
|
asynStatus readEncoderType();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
newPmacV3Controller *pC_;
|
||||||
|
|
||||||
|
void checkBounds(newPmacV3Controller *pController, int axisNo);
|
||||||
|
asynStatus readConfig();
|
||||||
|
bool initial_poll_;
|
||||||
|
bool waitForHandshake_;
|
||||||
|
time_t timeAtHandshake_;
|
||||||
|
time_t handshakeTimeout_;
|
||||||
|
time_t time_at_init_poll_;
|
||||||
|
time_t timeout_param_lib_init_;
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class newPmacV3Controller;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
813
sinqEPICSApp/src/newPmacV3Controller.cpp
Normal file
813
sinqEPICSApp/src/newPmacV3Controller.cpp
Normal file
@ -0,0 +1,813 @@
|
|||||||
|
|
||||||
|
#include "newPmacV3Controller.h"
|
||||||
|
#include "asynMotorController.h"
|
||||||
|
#include "asynOctetSyncIO.h"
|
||||||
|
#include "newPmacV3Axis.h"
|
||||||
|
#include <epicsExport.h>
|
||||||
|
#include <errlog.h>
|
||||||
|
#include <iocsh.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <registryFunction.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
static const char *driverName = "newPmacV3Controller";
|
||||||
|
|
||||||
|
// Static pointers (valid for the entire lifetime of the IOC). The number behind
|
||||||
|
// the strings gives the integer number of each variant (see also method
|
||||||
|
// stringifyAsynStatus)
|
||||||
|
static const char *asynSuccessStringified = "success"; // 0
|
||||||
|
static const char *asynTimeoutStringified = "timeout"; // 1
|
||||||
|
static const char *asynOverflowStringified = "overflow"; // 2
|
||||||
|
static const char *asynErrorStringified = "error"; // 3
|
||||||
|
static const char *asynDisconnectedStringified = "disconnected"; // 4
|
||||||
|
static const char *asynDisabledStringified = "disabled"; // 5
|
||||||
|
static const char *asynParamAlreadyExistsStringified =
|
||||||
|
"parameter already exists"; // 6
|
||||||
|
static const char *asynParamNotFoundStringified = "parameter not found"; // 7
|
||||||
|
static const char *asynParamWrongTypeStringified = "wrong type"; // 8
|
||||||
|
static const char *asynParamBadIndexStringified = "bad index"; // 9
|
||||||
|
static const char *asynParamUndefinedStringified = "parameter undefined"; // 10
|
||||||
|
static const char *asynParamInvalidListStringified = "invalid list"; // 11
|
||||||
|
|
||||||
|
const double newPmacV3Controller::TIMEOUT_ = 5.0; // seconds
|
||||||
|
|
||||||
|
/*
|
||||||
|
Constructor arguments
|
||||||
|
- portName:
|
||||||
|
- lowLevelPortName
|
||||||
|
- numAxes
|
||||||
|
- movingPollPeriod: Time between polls when moving (in seconds)
|
||||||
|
- idlePollPeriod: Time between polls when not moving (in seconds)
|
||||||
|
*/
|
||||||
|
newPmacV3Controller::newPmacV3Controller(const char *portName,
|
||||||
|
const char *lowLevelPortName,
|
||||||
|
int numAxes, double movingPollPeriod,
|
||||||
|
double idlePollPeriod,
|
||||||
|
const int &extraParams)
|
||||||
|
: asynMotorController(
|
||||||
|
portName, numAxes, NUM_MOTOR_DRIVER_PARAMS + extraParams,
|
||||||
|
0, // No additional interfaces beyond those in base class
|
||||||
|
0, // No additional callback interfaces beyond those in base class
|
||||||
|
ASYN_CANBLOCK | ASYN_MULTIDEVICE,
|
||||||
|
1, // autoconnect
|
||||||
|
0, 0) // Default priority and stack size
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
// Initialization of local variables
|
||||||
|
static const char *functionName =
|
||||||
|
"newPmacV3Controller::newPmacV3Controller";
|
||||||
|
asynStatus status = asynSuccess;
|
||||||
|
|
||||||
|
// Initialization of all member variables
|
||||||
|
lowLevelPortUser_ = nullptr;
|
||||||
|
|
||||||
|
// =========================================================================;
|
||||||
|
|
||||||
|
/*
|
||||||
|
We try to connect to the port via the port name provided by the constructor.
|
||||||
|
If this fails, the function is terminated via exit
|
||||||
|
*/
|
||||||
|
pasynOctetSyncIO->connect(lowLevelPortName, 0, &lowLevelPortUser_, NULL);
|
||||||
|
if (status != asynSuccess || lowLevelPortUser_ == nullptr) {
|
||||||
|
errlogPrintf("FATAL ERROR: in %s: cannot connect to MCU controller\n. "
|
||||||
|
"Terminating",
|
||||||
|
functionName);
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Create additional PVs
|
||||||
|
|
||||||
|
// MOTOR_MESSAGE_TEXT corresponds to the PV definition inside
|
||||||
|
// sinqn_asyn_motor.db. This text is used to forward status messages to
|
||||||
|
// NICOS and in turn to the user
|
||||||
|
status = createParam("MOTOR_MESSAGE_TEXT", asynParamOctet, &messageText_);
|
||||||
|
if (status != asynSuccess) {
|
||||||
|
paramLibAccessFailed(status, functionName, "messageText_");
|
||||||
|
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
|
"%s: FATAL ERROR: unable to create parameter (%s). "
|
||||||
|
"Terminating IOC.\n",
|
||||||
|
functionName, stringifyAsynStatus(status));
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
status = createParam("ENABLE_AXIS", asynParamInt32, &enableMotor_);
|
||||||
|
if (status != asynSuccess) {
|
||||||
|
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
|
"%s: FATAL ERROR: unable to create parameter (%s). "
|
||||||
|
"Terminating IOC.\n",
|
||||||
|
functionName, stringifyAsynStatus(status));
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
status = createParam("AXIS_ENABLED", asynParamInt32, &motorEnabled_);
|
||||||
|
if (status != asynSuccess) {
|
||||||
|
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
|
"%s: FATAL ERROR: unable to create parameter (%s). "
|
||||||
|
"Terminating IOC.\n",
|
||||||
|
functionName, stringifyAsynStatus(status));
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
status = createParam("ENCODER_TYPE", asynParamOctet, &encoderType_);
|
||||||
|
if (status != asynSuccess) {
|
||||||
|
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
|
"%s: FATAL ERROR: unable to create parameter (%s). "
|
||||||
|
"Terminating IOC.\n",
|
||||||
|
functionName, stringifyAsynStatus(status));
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
status = createParam("REREAD_ENCODER_POSITION", asynParamInt32,
|
||||||
|
&rereadEncoderPosition_);
|
||||||
|
if (status != asynSuccess) {
|
||||||
|
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
|
"%s: FATAL ERROR: unable to create parameter (%s). "
|
||||||
|
"Terminating IOC.\n",
|
||||||
|
functionName, stringifyAsynStatus(status));
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
status = createParam("REREAD_ENCODER_POSITION_RBV", asynParamInt32,
|
||||||
|
&rereadEncoderPositionRBV_);
|
||||||
|
if (status != asynSuccess) {
|
||||||
|
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
|
"%s: FATAL ERROR: unable to create parameter (%s). "
|
||||||
|
"Terminating IOC.\n",
|
||||||
|
functionName, stringifyAsynStatus(status));
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
status = createParam("READ_CONFIG", asynParamInt32, &readConfig_);
|
||||||
|
if (status != asynSuccess) {
|
||||||
|
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
|
"%s: FATAL ERROR: unable to create parameter (%s). "
|
||||||
|
"Terminating IOC.\n",
|
||||||
|
functionName, stringifyAsynStatus(status));
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
status =
|
||||||
|
createParam("MOTOR_POSITION_RBV", asynParamFloat64, &motorPositionRBV_);
|
||||||
|
if (status != asynSuccess) {
|
||||||
|
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
|
"%s: FATAL ERROR: unable to create parameter (%s). "
|
||||||
|
"Terminating IOC.\n",
|
||||||
|
functionName, stringifyAsynStatus(status));
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Define the end-of-string of a message coming from the device to EPICS.
|
||||||
|
It is not necessary to append a terminator to outgoing messages, since
|
||||||
|
the message length is encoded in the message header in the getSetResponse
|
||||||
|
method.
|
||||||
|
*/
|
||||||
|
const char *message_from_device =
|
||||||
|
"\006"; // Hex-code for ACK (acknowledge) -> Each message from the MCU
|
||||||
|
// is terminated by this value
|
||||||
|
status = pasynOctetSyncIO->setInputEos(
|
||||||
|
lowLevelPortUser_, message_from_device, strlen(message_from_device));
|
||||||
|
if (status != asynSuccess) {
|
||||||
|
asynPrint(
|
||||||
|
this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
|
"%s: FATAL ERROR: Unable to set input EOS (%s). Terminating IOC.\n",
|
||||||
|
functionName, stringifyAsynStatus(status));
|
||||||
|
pasynOctetSyncIO->disconnect(lowLevelPortUser_);
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
status = startPoller(movingPollPeriod, idlePollPeriod, 1);
|
||||||
|
if (status != asynSuccess) {
|
||||||
|
asynPrint(
|
||||||
|
lowLevelPortUser_, ASYN_TRACE_ERROR,
|
||||||
|
"%s: FATAL ERROR: Could not start poller (%s). Terminating IOC.\n",
|
||||||
|
functionName, stringifyAsynStatus(status));
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
status = callParamCallbacks();
|
||||||
|
if (status != asynSuccess) {
|
||||||
|
asynPrint(
|
||||||
|
lowLevelPortUser_, ASYN_TRACE_ERROR,
|
||||||
|
"%s: FATAL ERROR: Could not start poller (%s). Terminating IOC.\n",
|
||||||
|
functionName, stringifyAsynStatus(status));
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newPmacV3Controller::~newPmacV3Controller(void) {
|
||||||
|
/*
|
||||||
|
Cleanup of the memory allocated in the asynMotorController constructor
|
||||||
|
*/
|
||||||
|
free(this->pAxes_);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
newPmacV3Axis *newPmacV3Controller::getAxis(asynUser *pasynUser) {
|
||||||
|
asynMotorAxis *asynAxis = asynMotorController::getAxis(pasynUser);
|
||||||
|
return newPmacV3Controller::castToAxis(asynAxis);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Access one of the axes of the controller via the axis index.
|
||||||
|
If the axis does not exist or is not a Axis, the function must return Null
|
||||||
|
*/
|
||||||
|
newPmacV3Axis *newPmacV3Controller::getAxis(int axisNo) {
|
||||||
|
asynMotorAxis *asynAxis = asynMotorController::getAxis(axisNo);
|
||||||
|
return newPmacV3Controller::castToAxis(asynAxis);
|
||||||
|
}
|
||||||
|
|
||||||
|
newPmacV3Axis *newPmacV3Controller::castToAxis(asynMotorAxis *asynAxis) {
|
||||||
|
static const char *functionName = "newPmacV3Controller::getAxis";
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
// If the axis slot of the pAxes_ array is empty, a nullptr must be returned
|
||||||
|
if (asynAxis == nullptr) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Here, an error is emitted since asyn_axis is not a nullptr but also not
|
||||||
|
// an instance of Axis
|
||||||
|
newPmacV3Axis *axis = dynamic_cast<newPmacV3Axis *>(asynAxis);
|
||||||
|
if (axis == nullptr) {
|
||||||
|
asynPrint(lowLevelPortUser_, ASYN_TRACE_ERROR,
|
||||||
|
"%s: Axis %d is not an instance of newPmacV3Axis",
|
||||||
|
functionName, axis->axisNo_);
|
||||||
|
}
|
||||||
|
return axis;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Sends the given command to the axis specified by axisNo and returns the response
|
||||||
|
of the axis.
|
||||||
|
*/
|
||||||
|
asynStatus newPmacV3Controller::writeRead(int axisNo, const char *command,
|
||||||
|
char *response,
|
||||||
|
bool expect_response) {
|
||||||
|
// Definition of local variables.
|
||||||
|
static const char *functionName = "newPmacV3Controller::writeRead";
|
||||||
|
asynStatus status = asynSuccess;
|
||||||
|
asynStatus pl_status = asynSuccess;
|
||||||
|
char full_command[MAXBUF_] = {0};
|
||||||
|
char user_message[MAXBUF_] = {0};
|
||||||
|
int motorStatusProblem = 0;
|
||||||
|
|
||||||
|
// Send the message and block the thread until either a response has been
|
||||||
|
// received or the timeout is triggered
|
||||||
|
int eomReason = 0; // Flag indicating why the message has ended
|
||||||
|
// Number of bytes of the outgoing message (which is command + the
|
||||||
|
// end-of-string terminator defined in the constructor)
|
||||||
|
size_t nbytesOut = 0;
|
||||||
|
// Number of bytes of the incoming message (which is response + the
|
||||||
|
// end-of-string terminator defined in the constructor)
|
||||||
|
size_t nbytesIn = 0;
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
newPmacV3Axis *axis = getAxis(axisNo);
|
||||||
|
if (axis == nullptr) {
|
||||||
|
// We already did the error logging directly in getAxis
|
||||||
|
return asynError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
The message protocol of the pmacV3 used at PSI looks as follows (all
|
||||||
|
characters immediately following each other without a newline):
|
||||||
|
0x40 (ASCII value of @) -> Request for download
|
||||||
|
0xBF (ASCII value of ¿) -> Select mode "get_response"
|
||||||
|
0x00 (ASCII value of 0)
|
||||||
|
0x00 (ASCII value of 0)
|
||||||
|
0x00 (ASCII value of 0)
|
||||||
|
0x00 (ASCII value of 0)
|
||||||
|
0x00 (ASCII value of 0)
|
||||||
|
[message length in network byte order] -> Use the htons function for this
|
||||||
|
value [Actual message] It is not necessary to append a terminator, since
|
||||||
|
this protocol encodes the message length at the beginning. See Turbo PMAC
|
||||||
|
User Manual, page 418 in VR_PMAC_GETRESPONSE
|
||||||
|
|
||||||
|
The message has to be build manually into the buffer full_command, since it
|
||||||
|
contains NULL terminators in its middle, therefore the string manipulation
|
||||||
|
methods of C don't work.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// The entire message is equal to the command length
|
||||||
|
const size_t commandLength =
|
||||||
|
strlen(command) + 1; // +1 because of the appended /r
|
||||||
|
const int offset = 8;
|
||||||
|
|
||||||
|
// Positions 2 to 6 must have the value 0. Since full_command is initialized
|
||||||
|
// as an array of zeros, we don't need to set these bits manually.
|
||||||
|
full_command[0] = '\x40';
|
||||||
|
full_command[1] = '\xBF';
|
||||||
|
full_command[7] = commandLength;
|
||||||
|
|
||||||
|
snprintf((char *)full_command + offset, MAXBUF_ - offset, "%s\r", command);
|
||||||
|
asynPrint(lowLevelPortUser_, ASYN_TRACEIO_DRIVER,
|
||||||
|
"%s: Sending command: %s\n", functionName, full_command);
|
||||||
|
|
||||||
|
// Perform the actual writeRead
|
||||||
|
status = pasynOctetSyncIO->writeRead(
|
||||||
|
lowLevelPortUser_, full_command, commandLength + offset, response,
|
||||||
|
MAXBUF_, TIMEOUT_, &nbytesOut, &nbytesIn, &eomReason);
|
||||||
|
|
||||||
|
/*
|
||||||
|
If we expect a response, check if we got one. If no response was received,
|
||||||
|
flush the PMAC and try again. If that fails as well, return an error
|
||||||
|
*/
|
||||||
|
if (expect_response && strlen(response) == 0) {
|
||||||
|
// Flush message as defined in Turbo PMAC User Manual, p. 430:
|
||||||
|
// \x40\xB3000
|
||||||
|
// VR_DOWNLOAD = \x40
|
||||||
|
// VR_PMAC_FLUSH = \xB3
|
||||||
|
char flush_msg[5] = {0};
|
||||||
|
flush_msg[0] = '\x40';
|
||||||
|
flush_msg[1] = '\xB3';
|
||||||
|
size_t nbytesOut = 0;
|
||||||
|
status = pasynOctetSyncIO->write(lowLevelPortUser_, flush_msg, 5,
|
||||||
|
TIMEOUT_, &nbytesOut);
|
||||||
|
|
||||||
|
// Wait after the flush so the MCU has time to prepare for the
|
||||||
|
// next command
|
||||||
|
usleep(100000);
|
||||||
|
|
||||||
|
if (status == asynSuccess) {
|
||||||
|
// If flushing the MCU succeded, try to send the command again
|
||||||
|
status = pasynOctetSyncIO->writeRead(
|
||||||
|
lowLevelPortUser_, full_command, commandLength + offset,
|
||||||
|
response, MAXBUF_, TIMEOUT_, &nbytesOut, &nbytesIn, &eomReason);
|
||||||
|
|
||||||
|
// If the command returned an empty string for the second time, give
|
||||||
|
// up and propagate the error.
|
||||||
|
if (strlen(response) == 0) {
|
||||||
|
status = asynError;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
asynPrint(pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
|
"%s: Unable to flush MCU (%s).\n", functionName,
|
||||||
|
stringifyAsynStatus(status));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create custom error messages for different failure modes
|
||||||
|
switch (status) {
|
||||||
|
case asynSuccess:
|
||||||
|
break; // Communicate nothing
|
||||||
|
case asynTimeout:
|
||||||
|
snprintf(user_message, sizeof(user_message),
|
||||||
|
"connection timeout for axis %d", axisNo);
|
||||||
|
break;
|
||||||
|
case asynDisconnected:
|
||||||
|
snprintf(user_message, sizeof(user_message), "axis is not connected");
|
||||||
|
break;
|
||||||
|
case asynDisabled:
|
||||||
|
snprintf(user_message, sizeof(user_message), "axis is disabled");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
snprintf(user_message, sizeof(user_message),
|
||||||
|
"Communication failed (%s)", stringifyAsynStatus(status));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status != asynSuccess) {
|
||||||
|
// Check if the axis already is in an error communication mode. If it is
|
||||||
|
// not, upstream the error. This is done to avoid "flooding" the user
|
||||||
|
// with different error messages if more than one error ocurred before
|
||||||
|
// an error-free communication
|
||||||
|
|
||||||
|
pl_status =
|
||||||
|
getIntegerParam(axisNo, motorStatusProblem_, &motorStatusProblem);
|
||||||
|
if (pl_status != asynSuccess) {
|
||||||
|
return paramLibAccessFailed(pl_status, functionName,
|
||||||
|
"motorStatusProblem_");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (motorStatusProblem == 0) {
|
||||||
|
pl_status = axis->setStringParam(this->messageText_, user_message);
|
||||||
|
if (pl_status != asynSuccess) {
|
||||||
|
return paramLibAccessFailed(pl_status, functionName,
|
||||||
|
"messageText_");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log the overall status (communication successfull or not)
|
||||||
|
if (status == asynSuccess) {
|
||||||
|
asynPrint(lowLevelPortUser_, ASYN_TRACEIO_DRIVER,
|
||||||
|
"%s: device response: %s\n", functionName, response);
|
||||||
|
pl_status = axis->setIntegerParam(this->motorStatusCommsError_, 0);
|
||||||
|
} else {
|
||||||
|
asynPrint(lowLevelPortUser_, ASYN_TRACE_ERROR,
|
||||||
|
"%s: asynOctetSyncIO->writeRead failed for command %s on "
|
||||||
|
"axis %d (%s)\n",
|
||||||
|
functionName, command, axisNo, stringifyAsynStatus(status));
|
||||||
|
pl_status = axis->setIntegerParam(this->motorStatusCommsError_, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pl_status != asynSuccess) {
|
||||||
|
return paramLibAccessFailed(pl_status, functionName,
|
||||||
|
"motorStatusCommsError_");
|
||||||
|
}
|
||||||
|
|
||||||
|
return asynSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
asynStatus newPmacV3Controller::writeInt32(asynUser *pasynUser,
|
||||||
|
epicsInt32 value) {
|
||||||
|
|
||||||
|
int function = pasynUser->reason;
|
||||||
|
asynStatus status = asynSuccess;
|
||||||
|
static const char *functionName = "newPmacV3Controller::writeInt32";
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
newPmacV3Axis *axis = getAxis(pasynUser);
|
||||||
|
if (axis == nullptr) {
|
||||||
|
// We already did the error logging directly in getAxis
|
||||||
|
return asynError;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle custom PVs
|
||||||
|
if (function == enableMotor_) {
|
||||||
|
return axis->enable(value);
|
||||||
|
|
||||||
|
} else if (function == rereadEncoderPosition_) {
|
||||||
|
|
||||||
|
char encoderType[MAXBUF_] = {0};
|
||||||
|
|
||||||
|
/*
|
||||||
|
This is not a command that can always be run when enabling a
|
||||||
|
motor as it also causes relative encoders to reread a position
|
||||||
|
necessitating recalibration. We only want it to run on absolute
|
||||||
|
encoders. We also want it to be clear to instrument scientists, that
|
||||||
|
power has to be cut to the motor, in order to reread the encoder as not
|
||||||
|
all motors have breaks and they may start to move when disabled. For
|
||||||
|
that reason, we don't automatically disable the motors to run the
|
||||||
|
command and instead require that the scientists first disable the motor.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!value) {
|
||||||
|
return asynSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Poll the current status of the axis
|
||||||
|
bool moving = false;
|
||||||
|
status = axis->poll(&moving);
|
||||||
|
if (status != asynSuccess) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this is an absolute encoder
|
||||||
|
status = axis->readEncoderType();
|
||||||
|
if (status != asynSuccess) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
status = getStringParam(axis->axisNo_, encoderType_, encoderType);
|
||||||
|
if (status != asynSuccess) {
|
||||||
|
return paramLibAccessFailed(status, functionName, "encoderType_");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Abort if the axis is incremental
|
||||||
|
if (strcmp(encoderType, IncrementalEncoder) == 0) {
|
||||||
|
asynPrint(this->pasynUserSelf, ASYN_TRACE_WARNING,
|
||||||
|
"%s: Trying to reread absolute encoder of axis %d on "
|
||||||
|
"controller %s, but it is a relative encoder.\n",
|
||||||
|
functionName, axis->axisNo_, portName);
|
||||||
|
status = setStringParam(messageText_,
|
||||||
|
"Cannot reread an incremental encoder.");
|
||||||
|
if (status != asynSuccess) {
|
||||||
|
return paramLibAccessFailed(status, functionName,
|
||||||
|
"messageText_");
|
||||||
|
}
|
||||||
|
return asynError;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the axis is disabled. If not, inform the user that this
|
||||||
|
// is necessary
|
||||||
|
int enabled = 0;
|
||||||
|
status = getIntegerParam(axis->axisNo_, motorEnabled_, &enabled);
|
||||||
|
if (status != asynSuccess) {
|
||||||
|
return paramLibAccessFailed(status, functionName, "motorEnabled_");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enabled == 1) {
|
||||||
|
asynPrint(this->pasynUserSelf, ASYN_TRACE_WARNING,
|
||||||
|
"%s: Axis %d on controller %s must be disabled before "
|
||||||
|
"rereading the encoder.\n",
|
||||||
|
functionName, axis->axisNo_, portName);
|
||||||
|
status = setStringParam(
|
||||||
|
messageText_,
|
||||||
|
"Axis must be disabled before rereading the encoder.");
|
||||||
|
if (status != asynSuccess) {
|
||||||
|
return paramLibAccessFailed(status, functionName,
|
||||||
|
"messageText_");
|
||||||
|
}
|
||||||
|
return asynError;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Switching on the axis while the rereading process is still ongoing
|
||||||
|
// causes it to fail. We currently have no way to check if it is
|
||||||
|
// actually finished, so we instead wait for 0.5 seconds.
|
||||||
|
usleep(500000);
|
||||||
|
|
||||||
|
// turn off parameter as finished rereading
|
||||||
|
// this will only be immediately noticed in the read back variable
|
||||||
|
// though
|
||||||
|
status = axis->setIntegerParam(rereadEncoderPosition_, 0);
|
||||||
|
if (status != asynSuccess) {
|
||||||
|
return paramLibAccessFailed(status, functionName,
|
||||||
|
"rereadEncoderPosition_");
|
||||||
|
}
|
||||||
|
return asynSuccess;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
return asynMotorController::writeInt32(pasynUser, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Overloaded from asynMotorController because the special cases "motor enabling"
|
||||||
|
and "rereading the encoder" must be covered.
|
||||||
|
*/
|
||||||
|
asynStatus newPmacV3Controller::readInt32(asynUser *pasynUser,
|
||||||
|
epicsInt32 *value) {
|
||||||
|
|
||||||
|
int function = pasynUser->reason;
|
||||||
|
asynStatus status = asynError;
|
||||||
|
static const char *functionName = "newPmacV3Controller::readInt32";
|
||||||
|
|
||||||
|
// =====================================================================
|
||||||
|
|
||||||
|
newPmacV3Axis *axis = getAxis(pasynUser);
|
||||||
|
if (axis == nullptr) {
|
||||||
|
// We already did the error logging directly in getAxis
|
||||||
|
return asynError;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (function == rereadEncoderPositionRBV_) {
|
||||||
|
// Readback value for rereadEncoderPosition
|
||||||
|
status = getIntegerParam(axis->axisNo_, rereadEncoderPosition_, value);
|
||||||
|
if (status != asynSuccess) {
|
||||||
|
return paramLibAccessFailed(status, functionName,
|
||||||
|
"rereadEncoderPosition_");
|
||||||
|
}
|
||||||
|
status =
|
||||||
|
setIntegerParam(axis->axisNo_, rereadEncoderPositionRBV_, *value);
|
||||||
|
if (status != asynSuccess) {
|
||||||
|
return paramLibAccessFailed(status, functionName,
|
||||||
|
"rereadEncoderPositionRBV_");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the PVs from the parameter library
|
||||||
|
return callParamCallbacks();
|
||||||
|
} else {
|
||||||
|
return asynMotorController::readInt32(pasynUser, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *newPmacV3Controller::stringifyAsynStatus(asynStatus status) {
|
||||||
|
// See
|
||||||
|
// https://github.com/epics-modules/asyn/blob/master/asyn/asynDriver/asynDriver.h
|
||||||
|
// and
|
||||||
|
// https://github.com/epics-modules/asyn/blob/master/asyn/asynPortDriver/paramErrors.h
|
||||||
|
// for the definition of the error codes
|
||||||
|
switch (status) {
|
||||||
|
case asynSuccess:
|
||||||
|
return asynSuccessStringified;
|
||||||
|
case asynTimeout:
|
||||||
|
return asynTimeoutStringified;
|
||||||
|
case asynOverflow:
|
||||||
|
return asynOverflowStringified;
|
||||||
|
case asynError:
|
||||||
|
return asynErrorStringified;
|
||||||
|
case asynDisconnected:
|
||||||
|
return asynDisconnectedStringified;
|
||||||
|
case asynDisabled:
|
||||||
|
return asynDisabledStringified;
|
||||||
|
case asynParamAlreadyExists:
|
||||||
|
return asynParamAlreadyExistsStringified;
|
||||||
|
case asynParamNotFound:
|
||||||
|
return asynParamNotFoundStringified;
|
||||||
|
case asynParamWrongType:
|
||||||
|
return asynParamWrongTypeStringified;
|
||||||
|
case asynParamBadIndex:
|
||||||
|
return asynParamBadIndexStringified;
|
||||||
|
case asynParamUndefined:
|
||||||
|
return asynParamUndefinedStringified;
|
||||||
|
case asynParamInvalidList:
|
||||||
|
return asynParamInvalidListStringified;
|
||||||
|
}
|
||||||
|
|
||||||
|
asynPrint(this->lowLevelPortUser_, ASYN_TRACE_ERROR,
|
||||||
|
"newPmacV3Controller::stringifyAsynStatus: FATAL error: Reached "
|
||||||
|
"unreachable code. Terminating\n");
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
asynStatus newPmacV3Controller::paramLibAccessFailed(asynStatus status,
|
||||||
|
const char *functionName,
|
||||||
|
const char *parameter) {
|
||||||
|
char message[MAXBUF_] = {0};
|
||||||
|
snprintf(message, sizeof(message),
|
||||||
|
"Accessing the parameter library failed for parameter %s (%s). "
|
||||||
|
"This is a bug, please inform the software support.\n",
|
||||||
|
parameter, stringifyAsynStatus(status));
|
||||||
|
|
||||||
|
// Log the error message and try to propagate it
|
||||||
|
asynPrint(lowLevelPortUser_, ASYN_TRACE_ERROR, "%s: %s", functionName,
|
||||||
|
message);
|
||||||
|
setStringParam(messageText_, message);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
asynStatus newPmacV3Controller::checkNumExpectedReads(int expected, int read,
|
||||||
|
const char *functionName,
|
||||||
|
const char *command,
|
||||||
|
const char *response,
|
||||||
|
int axisNo_) {
|
||||||
|
if (expected == read) {
|
||||||
|
return asynSuccess;
|
||||||
|
} else {
|
||||||
|
char message[MAXBUF_] = {0};
|
||||||
|
snprintf(message, sizeof(message),
|
||||||
|
"Could not interpret response %s for command %s (axis %d). "
|
||||||
|
"This is a bug.",
|
||||||
|
response, command, axisNo_);
|
||||||
|
asynPrint(lowLevelPortUser_, ASYN_TRACE_ERROR, "%s: %s", functionName,
|
||||||
|
message);
|
||||||
|
setStringParam(messageText_, message);
|
||||||
|
setIntegerParam(motorStatusCommsError_, 1);
|
||||||
|
return asynError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************************************************************************************/
|
||||||
|
/** The following functions are C-wrappers, and can be called directly from
|
||||||
|
* iocsh */
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
/*
|
||||||
|
C wrapper for the Controller constructor.
|
||||||
|
*/
|
||||||
|
asynStatus newPmacV3CreateController(const char *portName,
|
||||||
|
const char *lowLevelPortName, int numAxes,
|
||||||
|
double movingPollPeriod,
|
||||||
|
double idlePollPeriod) {
|
||||||
|
/*
|
||||||
|
We create a new instance of CreateController, using the "new" keyword to
|
||||||
|
allocate it on the heap while avoiding RAII. TBD: Where is the pointer to
|
||||||
|
the controller stored?
|
||||||
|
https://github.com/epics-modules/motor/blob/master/motorApp/MotorSrc/asynMotorController.cpp
|
||||||
|
https://github.com/epics-modules/asyn/blob/master/asyn/asynPortDriver/asynPortDriver.cpp
|
||||||
|
|
||||||
|
Setting the pointer to nullptr / NULL immediately after construction is
|
||||||
|
simply done to avoid compiler warnings, see page 7 of this document:
|
||||||
|
https://subversion.xray.aps.anl.gov/synApps/measComp/trunk/documentation/measCompTutorial.pdf
|
||||||
|
*/
|
||||||
|
newPmacV3Controller *pController = new newPmacV3Controller(
|
||||||
|
portName, lowLevelPortName, numAxes, movingPollPeriod, idlePollPeriod);
|
||||||
|
pController = NULL;
|
||||||
|
|
||||||
|
return asynSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
C wrapper for the Axis constructor.
|
||||||
|
See Axis::Axis.
|
||||||
|
*/
|
||||||
|
asynStatus newPmacV3CreateAxis(const char *port, int axis) {
|
||||||
|
newPmacV3Axis *pAxis;
|
||||||
|
|
||||||
|
static const char *functionName = "newPmacV3CreateAxis";
|
||||||
|
|
||||||
|
/*
|
||||||
|
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(port);
|
||||||
|
if (ptr == nullptr) {
|
||||||
|
/*
|
||||||
|
We can't use asynPrint here since this macro would require us
|
||||||
|
to get a lowLevelPortUser_ from a pointer to an asynPortDriver.
|
||||||
|
However, the given pointer is a nullptr and therefore doesn't
|
||||||
|
have a lowLevelPortUser_! printf is an EPICS alternative which
|
||||||
|
works w/o that, but doesn't offer the comfort provided
|
||||||
|
by the asynTrace-facility
|
||||||
|
*/
|
||||||
|
printf("%s:%s: Error port %s not found\n", driverName, functionName,
|
||||||
|
port);
|
||||||
|
return asynError;
|
||||||
|
}
|
||||||
|
// Unsafe cast of the pointer to an asynPortDriver
|
||||||
|
asynPortDriver *apd = (asynPortDriver *)(ptr);
|
||||||
|
|
||||||
|
// Safe downcast
|
||||||
|
newPmacV3Controller *pC = dynamic_cast<newPmacV3Controller *>(apd);
|
||||||
|
if (pC == nullptr) {
|
||||||
|
printf("%s: controller on port %s is not a Controller\n", functionName,
|
||||||
|
port);
|
||||||
|
return asynError;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent manipulation of the controller from other threads while we create
|
||||||
|
// the new axis.
|
||||||
|
pC->lock();
|
||||||
|
|
||||||
|
/*
|
||||||
|
We create a new instance of Axis, using the "new" keyword to allocate it
|
||||||
|
on the heap while avoiding RAII. In the constructor, a pointer to the new
|
||||||
|
object is stored in the controller object "pC". Therefore, the axis instance
|
||||||
|
can still be reached later by quering "pC".
|
||||||
|
|
||||||
|
Setting the pointer to nullptr / NULL immediately after construction is
|
||||||
|
simply done to avoid compiler warnings, see page 7 of this document:
|
||||||
|
https://subversion.xray.aps.anl.gov/synApps/measComp/trunk/documentation/measCompTutorial.pdf
|
||||||
|
*/
|
||||||
|
pAxis = new newPmacV3Axis(pC, axis);
|
||||||
|
pAxis = nullptr;
|
||||||
|
|
||||||
|
// Allow manipulation of the controller again
|
||||||
|
pC->unlock();
|
||||||
|
return asynSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
This is boilerplate code which is used to make the FFI functions
|
||||||
|
CreateController and CreateAxis "known" to the IOC shell (iocsh).
|
||||||
|
TBD: If the code is compiled for running on vxWorks, this registration is
|
||||||
|
apparently not necessary?
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef vxWorks
|
||||||
|
#else
|
||||||
|
|
||||||
|
/*
|
||||||
|
Define name and type of the arguments for the CreateController function
|
||||||
|
in the iocsh. This is done by creating structs with the argument names and types
|
||||||
|
and then providing "factory" functions (configCreateControllerCallFunc).
|
||||||
|
These factory functions are used to register the constructors during
|
||||||
|
compilation.
|
||||||
|
*/
|
||||||
|
static const iocshArg CreateControllerArg0 = {"Controller port name",
|
||||||
|
iocshArgString};
|
||||||
|
static const iocshArg CreateControllerArg1 = {"Low level port name",
|
||||||
|
iocshArgString};
|
||||||
|
static const iocshArg CreateControllerArg2 = {"Number of axes", iocshArgInt};
|
||||||
|
static const iocshArg CreateControllerArg3 = {"Moving poll rate (s)",
|
||||||
|
iocshArgDouble};
|
||||||
|
static const iocshArg CreateControllerArg4 = {"Idle poll rate (s)",
|
||||||
|
iocshArgDouble};
|
||||||
|
static const iocshArg *const CreateControllerArgs[] = {
|
||||||
|
&CreateControllerArg0, &CreateControllerArg1, &CreateControllerArg2,
|
||||||
|
&CreateControllerArg3, &CreateControllerArg4};
|
||||||
|
static const iocshFuncDef configNewPmacV3CreateController = {
|
||||||
|
"newPmacV3CreateController", 5, CreateControllerArgs};
|
||||||
|
static void configNewPmacV3CreateControllerCallFunc(const iocshArgBuf *args) {
|
||||||
|
newPmacV3CreateController(args[0].sval, args[1].sval, args[2].ival,
|
||||||
|
args[3].dval, args[4].dval);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Same procedure as for the CreateController function, but for the axis
|
||||||
|
itself.
|
||||||
|
*/
|
||||||
|
static const iocshArg CreateAxisArg0 = {"Controller port name", iocshArgString};
|
||||||
|
static const iocshArg CreateAxisArg1 = {"Axis number", iocshArgInt};
|
||||||
|
static const iocshArg *const CreateAxisArgs[] = {&CreateAxisArg0,
|
||||||
|
&CreateAxisArg1};
|
||||||
|
static const iocshFuncDef configNewPmacV3CreateAxis = {"newPmacV3CreateAxis", 2,
|
||||||
|
CreateAxisArgs};
|
||||||
|
static void configNewPmacV3CreateAxisCallFunc(const iocshArgBuf *args) {
|
||||||
|
newPmacV3CreateAxis(args[0].sval, args[1].ival);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function is made known to EPICS in sinq.dbd and is called by EPICS
|
||||||
|
// in order to register both functions in the IOC shell
|
||||||
|
// TBD: Does this happen during compilation?
|
||||||
|
static void newPmacV3ControllerRegister(void) {
|
||||||
|
iocshRegister(&configNewPmacV3CreateController,
|
||||||
|
configNewPmacV3CreateControllerCallFunc);
|
||||||
|
iocshRegister(&configNewPmacV3CreateAxis,
|
||||||
|
configNewPmacV3CreateAxisCallFunc);
|
||||||
|
}
|
||||||
|
epicsExportRegistrar(newPmacV3ControllerRegister);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // extern "C"
|
92
sinqEPICSApp/src/newPmacV3Controller.h
Normal file
92
sinqEPICSApp/src/newPmacV3Controller.h
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
/********************************************
|
||||||
|
* pmacV3Controller.h
|
||||||
|
*
|
||||||
|
* PMAC V3 controller driver based on the asynMotorController class
|
||||||
|
*
|
||||||
|
* Stefan Mathis, September 2024
|
||||||
|
********************************************/
|
||||||
|
|
||||||
|
#ifndef pmacV3Controller_H
|
||||||
|
#define pmacV3Controller_H
|
||||||
|
#include "asynMotorAxis.h"
|
||||||
|
#include "asynMotorController.h"
|
||||||
|
#include "newPmacV3Axis.h"
|
||||||
|
|
||||||
|
#define IncrementalEncoder "Incremental encoder"
|
||||||
|
#define AbsoluteEncoder "Absolute encoder"
|
||||||
|
|
||||||
|
class newPmacV3Controller : public asynMotorController {
|
||||||
|
|
||||||
|
public:
|
||||||
|
newPmacV3Controller(const char *portName, const char *lowLevelPortName,
|
||||||
|
int numAxes, double movingPollPeriod,
|
||||||
|
double idlePollPeriod, const int &extraParams = 2);
|
||||||
|
|
||||||
|
virtual ~newPmacV3Controller();
|
||||||
|
|
||||||
|
/* Overloaded methods methods */
|
||||||
|
newPmacV3Axis *getAxis(asynUser *pasynUser);
|
||||||
|
newPmacV3Axis *getAxis(int axisNo);
|
||||||
|
|
||||||
|
// overloaded because we want to enable/disable the motor
|
||||||
|
asynStatus writeInt32(asynUser *pasynUser, epicsInt32 value);
|
||||||
|
|
||||||
|
// overloaded because we want to read the axis state
|
||||||
|
asynStatus readInt32(asynUser *pasynUser, epicsInt32 *value);
|
||||||
|
|
||||||
|
// // Overloaded to read configuration details from the motor records
|
||||||
|
// asynStatus drvUserCreate(asynUser *pasynUser, const char *drvInfo,
|
||||||
|
// const char **pptypeName, size_t *psize);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
asynUser *lowLevelPortUser_;
|
||||||
|
|
||||||
|
asynStatus writeRead(int axisNo, const char *command, char *response,
|
||||||
|
bool expect_response);
|
||||||
|
|
||||||
|
// Create a descriptive string out of an asynStatus which can be used for
|
||||||
|
// logging or communicating with the user
|
||||||
|
const char *stringifyAsynStatus(asynStatus status);
|
||||||
|
asynStatus paramLibAccessFailed(asynStatus status, const char *functionName,
|
||||||
|
const char *parameter);
|
||||||
|
asynStatus checkNumExpectedReads(int expected, int read,
|
||||||
|
const char *functionName,
|
||||||
|
const char *command, const char *response,
|
||||||
|
int axisNo_);
|
||||||
|
newPmacV3Axis *castToAxis(asynMotorAxis *asynAxis);
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Set the maximum buffer size. This is an empirical value which must be
|
||||||
|
// large enough to avoid overflows for all commands to the device /
|
||||||
|
// responses from it.
|
||||||
|
static const uint32_t MAXBUF_ = 200;
|
||||||
|
|
||||||
|
/*
|
||||||
|
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. This variable has to be specified in the
|
||||||
|
.cpp-file.
|
||||||
|
*/
|
||||||
|
static const double TIMEOUT_;
|
||||||
|
|
||||||
|
// Indices of additional PVs
|
||||||
|
int messageText_;
|
||||||
|
int enableMotor_;
|
||||||
|
int motorEnabled_;
|
||||||
|
int rereadEncoderPosition_;
|
||||||
|
int rereadEncoderPositionRBV_;
|
||||||
|
int readConfig_;
|
||||||
|
int encoderType_;
|
||||||
|
int motorPositionRBV_;
|
||||||
|
|
||||||
|
/*
|
||||||
|
If the time between two sent messages is too short, the MCU communication
|
||||||
|
module might "lose" an answer. To prevent this, a small delay is introduced
|
||||||
|
in EPICS after each message exchange. Unit is microseconds.
|
||||||
|
*/
|
||||||
|
int afterMessageSleep_;
|
||||||
|
|
||||||
|
friend class newPmacV3Axis;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* pmacV3Controller_H */
|
@ -10,6 +10,7 @@ registrar(C804ControllerRegister)
|
|||||||
registrar(pmacAsynIPPortRegister)
|
registrar(pmacAsynIPPortRegister)
|
||||||
registrar(MasterMACSRegister)
|
registrar(MasterMACSRegister)
|
||||||
registrar(SINQControllerRegister)
|
registrar(SINQControllerRegister)
|
||||||
|
registrar(newPmacV3ControllerRegister)
|
||||||
|
|
||||||
#--------------------------------------------------------
|
#--------------------------------------------------------
|
||||||
# With the PSI module build system, including these items actually
|
# With the PSI module build system, including these items actually
|
||||||
|
Reference in New Issue
Block a user