Compare commits

...

4 Commits
0.3.0 ... 0.4.3

Author SHA1 Message Date
08d76d7953 bump sinqMotor version to 0.6.3 2025-01-08 16:04:58 +01:00
1f02001502 Various small improvements to documentation, error messages etc.
Also moved the initialization of some parameters to sinqMotor
2024-12-23 09:32:00 +01:00
2f2678546d Bumped the required version of sinqMotor 2024-12-11 09:50:22 +01:00
285fab7587 Refactored some code into sinqMotor:
- Enable, EnableRBV and CanDisable
- EncoderType
- Removed function isEnabled as it is no longer required from sinqMotor
0.5.0
2024-12-09 11:20:16 +01:00
7 changed files with 96 additions and 120 deletions

View File

@ -11,7 +11,7 @@ REQUIRED+=asynMotor
REQUIRED+=sinqMotor
# Specify the version of sinqMotor we want to build against
sinqMotor_VERSION=0.4.0
sinqMotor_VERSION=0.6.3
# These headers allow to depend on this library for derived drivers.
HEADERS += src/pmacv3Axis.h

View File

@ -8,6 +8,10 @@ This is a driver for the pmacV3 motion controller with the SINQ communication pr
This driver is a standard sinqMotor-derived driver and does not need any specific configuration. For the general configuration, please see https://git.psi.ch/sinq-epics-modules/sinqmotor/-/blob/main/README.md.
The folder "utils" contains utility scripts for working with pmac motor controllers:
- writeRead.py: Allows sending commands to and receiving commands from a pmac controller over an ethernet connection.
- analyzeTcpDump: This script takes a tcpdump as an input and
## Developer guide
### Usage in IOC shell

View File

@ -1,18 +1,7 @@
# Read out the encoder type in human-readable form. The output numbers can be
# interpreted as ASCII.
# This record is coupled to the parameter library via encoderType -> ENCODER_TYPE.
record(waveform, "$(INSTR)$(M):Encoder_Type") {
field(DTYP, "asynOctetRead")
field(INP, "@asyn($(CONTROLLER),$(AXIS),1) ENCODER_TYPE")
field(FTVL, "CHAR")
field(NELM, "80")
field(SCAN, "I/O Intr")
}
# Trigger a rereading of the encoder. This action is sometimes necessary for
# absolute encoders after enabling them. For incremental encoders, this is a no-op.
# This record is coupled to the parameter library via rereadEncoderPosition_ -> REREAD_ENCODER_POSITION.
record(longout, "$(INSTR)$(M):Reread_Encoder") {
record(longout, "$(INSTR)$(M):RereadEncoder") {
field(DTYP, "asynInt32")
field(OUT, "@asyn($(CONTROLLER),$(AXIS),1) REREAD_ENCODER_POSITION")
field(PINI, "NO")
@ -23,7 +12,7 @@ record(longout, "$(INSTR)$(M):Reread_Encoder") {
# once at IOC startup during atFirstPoll. However, it can be triggered manually
# by setting this record value to 1.
# This record is coupled to the parameter library via readConfig_ -> READ_CONFIG.
record(longout, "$(INSTR)$(M):Read_Config") {
record(longout, "$(INSTR)$(M):ReadConfig") {
field(DTYP, "asynInt32")
field(OUT, "@asyn($(CONTROLLER),$(AXIS),1) READ_CONFIG")
field(PINI, "NO")

View File

@ -50,7 +50,8 @@ pmacv3Axis::pmacv3Axis(pmacv3Controller *pC, int axisNo)
exit(-1);
}
status = pC_->setDoubleParam(axisNo_, pC_->motorPosition_, 0.0);
// pmacv3 motors can always be disabled
status = pC_->setIntegerParam(axisNo_, pC_->motorCanDisable_, 1);
if (status != asynSuccess) {
asynPrint(
pC_->pasynUserSelf, ASYN_TRACE_ERROR,
@ -73,12 +74,7 @@ pmacv3Axis::~pmacv3Axis(void) {
/**
Read the configuration at the first poll
*/
asynStatus pmacv3Axis::atFirstPoll() { return readConfig(); }
/*
Read the configuration from the motor control unit and the parameter library.
*/
asynStatus pmacv3Axis::readConfig() {
asynStatus pmacv3Axis::atFirstPoll() {
// Local variable declaration
asynStatus status = asynSuccess;
@ -102,7 +98,10 @@ asynStatus pmacv3Axis::readConfig() {
__PRETTY_FUNCTION__, __LINE__);
}
// Software limits and current position
/*
Read out the axis status, the current position, current and maximum speed,
acceleration and the air cushion delay.
*/
snprintf(command, sizeof(command),
"P%2.2d00 Q%2.2d10 Q%2.2d03 Q%2.2d04 Q%2.2d06 P%2.2d22", axisNo_,
axisNo_, axisNo_, axisNo_, axisNo_, axisNo_);
@ -241,7 +240,7 @@ asynStatus pmacv3Axis::doPoll(bool *moving) {
}
// Transform from EPICS to motor coordinates (see comment in
// pmacv3Axis::readConfig())
// pmacv3Axis::atFirstPoll)
previousPosition = previousPosition * motorRecResolution;
// Query the axis status
@ -264,13 +263,13 @@ asynStatus pmacv3Axis::doPoll(bool *moving) {
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
other words, the EPICS/NICOS limits should be stricter than the software
limits on the MCU which in turn should be stricter than the hardware limit
switches. For example, if the hardware limit switches are at [-10, 10], the
software limits could be at [-9, 9] and the EPICS / NICOS limits could be at
[-8, 8]. Therefore, we cannot use the software limits read from the MCU
directly, but need to shrink them a bit. In this case, we're shrinking them
by 0.1 mm or 0.1 degree (depending on the axis type) on both sides.
by limitsOffset on both sides.
*/
pl_status =
pC_->getDoubleParam(axisNo_, pC_->motorLimitsOffset_, &limitsOffset);
@ -284,6 +283,14 @@ asynStatus pmacv3Axis::doPoll(bool *moving) {
// Store the axis status
axisStatus_ = axStatus;
// Update the enablement PV
pl_status = setIntegerParam(pC_->motorEnableRBV_,
(axStatus != -3 && axStatus != -5));
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorEnableRBV_",
__PRETTY_FUNCTION__, __LINE__);
}
// Intepret the status
switch (axStatus) {
case -6:
@ -514,7 +521,7 @@ asynStatus pmacv3Axis::doPoll(bool *moving) {
snprintf(command, sizeof(command),
"Maximum allowed following error exceeded (P%2.2d01 = %d). "
"Check if movement range is blocked."
"Check if movement range is blocked. "
"Otherwise please call the support.",
axisNo_, error);
pl_status = setStringParam(pC_->motorMessageText_, command);
@ -608,13 +615,6 @@ asynStatus pmacv3Axis::doPoll(bool *moving) {
}
}
pl_status = setIntegerParam(pC_->motorEnableRBV_,
(axStatus != -3 && axStatus != -5));
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorEnableRBV_",
__PRETTY_FUNCTION__, __LINE__);
}
pl_status = setIntegerParam(pC_->motorStatusMoving_, *moving);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorStatusMoving_",
@ -648,7 +648,7 @@ asynStatus pmacv3Axis::doPoll(bool *moving) {
}
// Transform from motor to EPICS coordinates (see comment in
// pmacv3Axis::readConfig())
// pmacv3Axis::atFirstPoll())
currentPosition = currentPosition / motorRecResolution;
pl_status = setDoubleParam(pC_->motorPosition_, currentPosition);
@ -950,7 +950,11 @@ asynStatus pmacv3Axis::rereadEncoder() {
}
// Abort if the axis is incremental
if (strcmp(encoderType, IncrementalEncoder) == 1) {
if (strcmp(encoderType, IncrementalEncoder) == 0) {
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW,
"%s => line %d:\nEncoder of axis %d is not reread because it "
"is incremental.\n",
__PRETTY_FUNCTION__, __LINE__, axisNo_);
return asynSuccess;
}
@ -1123,8 +1127,3 @@ asynStatus pmacv3Axis::enable(bool on) {
}
return asynError;
}
asynStatus pmacv3Axis::isEnabled(bool *on) {
*on = (axisStatus_ != -3 && axisStatus_ != -5);
return asynSuccess;
}

View File

@ -88,15 +88,6 @@ class pmacv3Axis : public sinqAxis {
*/
asynStatus enable(bool on);
/**
* @brief EThis function sets "on" to true, if the motor is enabled, and to
* false otherwise
*
* @param on
* @return asynStatus
*/
asynStatus isEnabled(bool *on);
/**
* @brief Read the encoder type (incremental or absolute) for this axis from
* the MCU and store the information in the PV ENCODER_TYPE.
@ -115,7 +106,6 @@ class pmacv3Axis : public sinqAxis {
protected:
pmacv3Controller *pC_;
asynStatus readConfig();
bool initial_poll_;
bool waitForHandshake_;
time_t timeAtHandshake_;

View File

@ -1,6 +1,3 @@
// Needed to use strcpy_s from string.h
#define __STDC_WANT_LIB_EXT1__ 1
#include "pmacv3Controller.h"
#include "asynMotorController.h"
#include "asynOctetSyncIO.h"
@ -14,21 +11,21 @@
#include <unistd.h>
/**
* @brief Copy src into dst and replace all carriage returns with spaces
* @brief Copy src into dst and replace all carriage returns with spaces. This
* allows to print *dst with asynPrint.
*
*
* @param dst Buffer for the modified string
* @param src Original string
*/
void adjustResponseForPrint(char *dst, const char *src) {
// Needed to use strcpy_s from string.h
#ifdef __STDC_LIB_EXT1__
strcpy_s(dst, src);
for (size_t i = 0; i < strlen(dst); i++) {
if (dst[i] == '\r') {
dst[i] = '_';
void adjustResponseForPrint(char *dst, const char *src, size_t buf_length) {
for (size_t i = 0; i < buf_length; i++) {
if (src[i] == '\r') {
dst[i] = ' ';
} else {
dst[i] = src[i];
}
}
#endif
}
/**
@ -51,12 +48,10 @@ pmacv3Controller::pmacv3Controller(const char *portName,
portName, ipPortConfigName, numAxes, movingPollPeriod, idlePollPeriod,
/*
The following parameter library entries are added in this driver:
- ENCODER_TYPE
- REREAD_ENCODER_POSITION
- READ_CONFIG
- ACCEL_FROM_DRIVER
*/
4)
NUM_PMACV3_DRIVER_PARAMS)
{
@ -85,15 +80,6 @@ pmacv3Controller::pmacv3Controller(const char *portName,
// =========================================================================
// Create additional parameter library entries
status = createParam("ENCODER_TYPE", asynParamOctet, &encoderType_);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (creating a parameter failed "
"with %s).\nTerminating IOC",
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status));
exit(-1);
}
status = createParam("REREAD_ENCODER_POSITION", asynParamInt32,
&rereadEncoderPosition_);
if (status != asynSuccess) {
@ -307,14 +293,14 @@ asynStatus pmacv3Controller::writeRead(int axisNo, const char *command,
// Second check: If this fails, give up and propagate the error.
if (numExpectedResponses != numReceivedResponses) {
adjustResponseForPrint(modResponse, response);
adjustResponseForPrint(modResponse, response, MAXBUF_);
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nUnexpected response %s (_ are "
"carriage returns) for command %s\n",
"%s => line %d:\nUnexpected response '%s' (carriage "
"returns are replaced with spaces) for command %s\n",
__PRETTY_FUNCTION__, __LINE__, modResponse, command);
snprintf(drvMessageText, sizeof(drvMessageText),
"Received unexpected response %s (_ are "
"carriage returns) for command %s. "
"Received unexpected response '%s' (carriage returns "
"are replaced with spaces) for command %s. "
"Please call the support",
modResponse, command);
pl_status = setStringParam(motorMessageText_, drvMessageText);
@ -356,29 +342,6 @@ asynStatus pmacv3Controller::writeRead(int axisNo, const char *command,
}
}
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, "motorStatusProblem_",
__PRETTY_FUNCTION__, __LINE__);
}
if (motorStatusProblem == 0) {
pl_status =
axis->setStringParam(this->motorMessageText_, drvMessageText);
if (pl_status != asynSuccess) {
return paramLibAccessFailed(pl_status, "motorMessageText_",
__PRETTY_FUNCTION__, __LINE__);
}
}
}
// Log the overall status (communication successfull or not)
if (status == asynSuccess) {
@ -388,21 +351,43 @@ asynStatus pmacv3Controller::writeRead(int axisNo, const char *command,
__PRETTY_FUNCTION__, __LINE__, modResponse);
pl_status = axis->setIntegerParam(this->motorStatusCommsError_, 0);
} else {
if (status == asynSuccess) {
asynPrint(
lowLevelPortUser_, ASYN_TRACE_ERROR,
"%s => line %d:\nCommunication failed for command %s (%s)\n",
__PRETTY_FUNCTION__, __LINE__, fullCommand,
stringifyAsynStatus(status));
pl_status = axis->setIntegerParam(this->motorStatusCommsError_, 1);
}
asynPrint(lowLevelPortUser_, ASYN_TRACE_ERROR,
"%s => line %d:\nCommunication failed for command %s (%s)\n",
__PRETTY_FUNCTION__, __LINE__, fullCommand,
stringifyAsynStatus(status));
// 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, "motorStatusCommsError_",
return paramLibAccessFailed(pl_status, "motorStatusProblem_",
__PRETTY_FUNCTION__, __LINE__);
}
if (motorStatusProblem == 0) {
pl_status = axis->setStringParam(motorMessageText_, drvMessageText);
if (pl_status != asynSuccess) {
return paramLibAccessFailed(pl_status, "motorMessageText_",
__PRETTY_FUNCTION__, __LINE__);
}
pl_status = axis->setIntegerParam(motorStatusProblem_, 1);
if (pl_status != asynSuccess) {
return paramLibAccessFailed(pl_status, "motorStatusProblem",
__PRETTY_FUNCTION__, __LINE__);
}
pl_status = axis->setIntegerParam(motorStatusProblem_, 1);
if (pl_status != asynSuccess) {
return paramLibAccessFailed(pl_status, "motorStatusCommsError_",
__PRETTY_FUNCTION__, __LINE__);
}
}
}
return asynSuccess;
return status;
}
asynStatus pmacv3Controller::writeInt32(asynUser *pasynUser, epicsInt32 value) {
@ -420,17 +405,27 @@ asynStatus pmacv3Controller::writeInt32(asynUser *pasynUser, epicsInt32 value) {
if (function == rereadEncoderPosition_) {
return axis->rereadEncoder();
} else if (function == readConfig_) {
return axis->readConfig();
return axis->atFirstPoll();
} else {
return sinqController::writeInt32(pasynUser, value);
}
}
asynStatus sinqController::readInt32(asynUser *pasynUser, epicsInt32 *value) {
// PMACs can be disabled
if (pasynUser->reason == motorCanDisable_) {
*value = 1;
return asynSuccess;
} else {
return asynMotorController::readInt32(pasynUser, value);
}
}
asynStatus pmacv3Controller::errMsgCouldNotParseResponse(
const char *command, const char *response, int axisNo,
const char *functionName, int lineNumber) {
char modifiedResponse[MAXBUF_] = {0};
adjustResponseForPrint(modifiedResponse, response);
adjustResponseForPrint(modifiedResponse, response, MAXBUF_);
return sinqController::errMsgCouldNotParseResponse(
command, modifiedResponse, axisNo, functionName, lineNumber);
}
@ -446,7 +441,7 @@ C wrapper for the controller constructor. Please refer to the pmacv3Controller
constructor documentation.
*/
asynStatus pmacv3CreateController(const char *portName,
const char *lowLevelPortName, int numAxes,
const char *ipPortConfigName, int numAxes,
double movingPollPeriod,
double idlePollPeriod, double comTimeout) {
/*
@ -461,7 +456,7 @@ asynStatus pmacv3CreateController(const char *portName,
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-variable"
pmacv3Controller *pController =
new pmacv3Controller(portName, lowLevelPortName, numAxes,
new pmacv3Controller(portName, ipPortConfigName, numAxes,
movingPollPeriod, idlePollPeriod, comTimeout);
return asynSuccess;

View File

@ -12,9 +12,6 @@
#include "sinqAxis.h"
#include "sinqController.h"
#define IncrementalEncoder "Incremental encoder"
#define AbsoluteEncoder "Absolute encoder"
class pmacv3Controller : public sinqController {
public:
@ -127,11 +124,13 @@ class pmacv3Controller : public sinqController {
double comTimeout_;
// Indices of additional PVs
#define FIRST_PMACV3_PARAM rereadEncoderPosition_
int rereadEncoderPosition_;
int readConfig_;
int encoderType_;
#define LAST_PMACV3_PARAM readConfig_
friend class pmacv3Axis;
};
#define NUM_PMACV3_DRIVER_PARAMS (&LAST_PMACV3_PARAM - &FIRST_PMACV3_PARAM + 1)
#endif /* pmacv3Controller_H */