Compare commits

...

11 Commits
0.1.1 ... 0.4.2

Author SHA1 Message Date
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
3ee507086a Included the possibility to vary the motor speed. 2024-12-06 08:30:10 +01:00
2e2c24238b Prototype for v0.2 2024-12-04 13:39:36 +01:00
e967e65d33 Added support for (optional) variable speed drive mode and refactored
some records into sinqMotor
2024-11-29 14:54:05 +01:00
dc70b560f7 Improved the error message when the MCU response is printed and the IOC
shell constructor documentation.
2024-11-27 16:05:48 +01:00
a6227629ad Improved the error message when the MCU response is printed. 2024-11-27 15:51:03 +01:00
2dd46cc48d Improved the error message when the error is printed. 2024-11-27 15:36:51 +01:00
35ade9e78d Substantial rework of the driver after CAMEA testing showed various
flaws.
2024-11-26 16:59:18 +01:00
a69ffe8134 Changed sinqMotor dependency to 0.2.0 2024-11-21 15:38:38 +01:00
9 changed files with 403 additions and 489 deletions

View File

@ -47,11 +47,11 @@ build_module:
- sed -i 's/ARCH_FILTER=.*/ARCH_FILTER=linux%/' Makefile
- echo "LIBVERSION=${CI_COMMIT_TAG:-0.0.1}" >> Makefile
- make install
- cp -rT "/ioc/modules/pmacV3/$(ls -U /ioc/modules/pmacV3/ | head -1)" "./pmacV3-${CI_COMMIT_TAG:-$CI_COMMIT_SHORT_SHA}"
- cp -rT "/ioc/modules/pmacv3/$(ls -U /ioc/modules/pmacv3/ | head -1)" "./pmacv3-${CI_COMMIT_TAG:-$CI_COMMIT_SHORT_SHA}"
artifacts:
name: "pmacV3-${CI_COMMIT_TAG:-$CI_COMMIT_SHORT_SHA}"
name: "pmacv3-${CI_COMMIT_TAG:-$CI_COMMIT_SHORT_SHA}"
paths:
- "pmacV3-${CI_COMMIT_TAG:-$CI_COMMIT_SHORT_SHA}/*"
- "pmacv3-${CI_COMMIT_TAG:-$CI_COMMIT_SHORT_SHA}/*"
expire_in: 1 week
when: always
tags:

View File

@ -1,7 +1,7 @@
# Use the PSI build system
include /ioc/tools/driver.makefile
MODULE=pmacV3
MODULE=pmacv3
BUILDCLASSES=Linux
EPICS_VERSIONS=7.0.7
ARCH_FILTER=RHEL%
@ -11,17 +11,20 @@ REQUIRED+=asynMotor
REQUIRED+=sinqMotor
# Specify the version of sinqMotor we want to build against
sinqMotor_VERSION=0.1
sinqMotor_VERSION=0.6.2
# These headers allow to depend on this library for derived drivers.
HEADERS += src/pmacV3Axis.h
HEADERS += src/pmacV3Controller.h
HEADERS += src/pmacv3Axis.h
HEADERS += src/pmacv3Controller.h
# Source files to build
SOURCES += src/pmacV3Axis.cpp
SOURCES += src/pmacV3Controller.cpp
SOURCES += src/pmacv3Axis.cpp
SOURCES += src/pmacv3Controller.cpp
# Store the record files
TEMPLATES += db/pmacv3.db
# This file registers the motor-specific functions in the IOC shell.
DBDS += src/pmacV3.dbd
DBDS += src/pmacv3.dbd
USR_CFLAGS += -Wall -Wextra # -Werror
USR_CFLAGS += -Wall -Wextra -Weffc++ -Wunused-result # -Werror

View File

@ -1,20 +1,46 @@
# pmacV3
# pmacv3
## Overview
This is a driver for the PMac V3 motion controller with the SINQ communication protocol. It is based on the sinqMotor shared library (https://git.psi.ch/sinq-epics-modules/sinqmotor). The header files contain detailed documentation for all public functions. The headers themselves are exported when building the library to allow other drivers to depend on this one.
This is a driver for the pmacV3 motion controller with the SINQ communication protocol. It is based on the sinqMotor shared library (https://git.psi.ch/sinq-epics-modules/sinqmotor). The header files contain detailed documentation for all public functions. The headers themselves are exported when building the library to allow other drivers to depend on this one.
## Usage in IOC shell
## User guide
pmacV3 exposes the following IOC shell functions (all in pmacV3Controller.cpp):
- `pmacV3CreateController`: Create a new controller object.
- `pmacV3CreateAxis`: Create a new axis object.
The function arguments are documented directly within the source code.
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.
## Versioning
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
pmacv3 exposes the following IOC shell functions (all in pmacv3Controller.cpp):
- `pmacv3Controller`: Create a new controller object.
- `pmacv3Axis`: Create a new axis object.
These functions are parametrized as follows:
```
pmacv3Controller(
"$(NAME)", # Name of the MCU, e.g. mcu1. This parameter should be provided by an environment variable.
"$(ASYN_PORT)", # IP-Port of the MCU. This parameter should be provided by an environment variable.
8, # Maximum number of axes
0.05, # Busy poll period in seconds
1, # Idle poll period in seconds
0.05 # Communication timeout in seconds
);
```
```
pmacv3Axis(
"$(NAME)", # Name of the associated MCU, e.g. mcu1. This parameter should be provided by an environment variable.
1 # Index of the axis.
);
```
### Versioning
Please see the documentation for the module sinqMotor: https://git.psi.ch/sinq-epics-modules/sinqmotor/-/blob/main/README.md.
## How to build it
### How to build it
Please see the documentation for the module sinqMotor: https://git.psi.ch/sinq-epics-modules/sinqmotor/-/blob/main/README.md.

19
db/pmacv3.db Normal file
View File

@ -0,0 +1,19 @@
# 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):RereadEncoder") {
field(DTYP, "asynInt32")
field(OUT, "@asyn($(CONTROLLER),$(AXIS),1) REREAD_ENCODER_POSITION")
field(PINI, "NO")
}
# The pmacV3 driver reads certain configuration parameters (such as the velocity
# and the acceleration) directly from the MCU. This reading procedure is performed
# 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):ReadConfig") {
field(DTYP, "asynInt32")
field(OUT, "@asyn($(CONTROLLER),$(AXIS),1) READ_CONFIG")
field(PINI, "NO")
}

View File

@ -1,4 +1,4 @@
#---------------------------------------------
# SINQ specific DB definitions
#---------------------------------------------
registrar(pmacV3Register)
registrar(pmacv3Register)

View File

@ -1,6 +1,6 @@
#include "pmacV3Axis.h"
#include "pmacv3Axis.h"
#include "asynOctetSyncIO.h"
#include "pmacV3Controller.h"
#include "pmacv3Controller.h"
#include <cmath>
#include <errlog.h>
#include <limits>
@ -8,7 +8,7 @@
#include <string.h>
#include <unistd.h>
pmacV3Axis::pmacV3Axis(pmacV3Controller *pC, int axisNo, double offsetLimit)
pmacv3Axis::pmacv3Axis(pmacv3Controller *pC, int axisNo)
: sinqAxis(pC, axisNo), pC_(pC) {
asynStatus status = asynSuccess;
@ -38,7 +38,6 @@ pmacV3Axis::pmacV3Axis(pmacV3Controller *pC, int axisNo, double offsetLimit)
initial_poll_ = true;
waitForHandshake_ = false;
axisStatus_ = 0;
offsetLimits_ = offsetLimit;
// Provide initial values for some parameter library entries
status = pC_->setIntegerParam(axisNo_, pC_->rereadEncoderPosition_, 0);
@ -51,7 +50,8 @@ pmacV3Axis::pmacV3Axis(pmacV3Controller *pC, int axisNo, double offsetLimit)
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,
@ -66,7 +66,7 @@ pmacV3Axis::pmacV3Axis(pmacV3Controller *pC, int axisNo, double offsetLimit)
scaleMovTimeout_ = 2.0;
}
pmacV3Axis::~pmacV3Axis(void) {
pmacv3Axis::~pmacv3Axis(void) {
// Since the controller memory is managed somewhere else, we don't need to
// clean up the pointer pC here.
}
@ -74,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;
@ -87,60 +82,13 @@ asynStatus pmacV3Axis::readConfig() {
int nvals = 0;
double motorRecResolution = 0.0;
double motorPosition = 0.0;
double motorVelBase = 0.0;
double motorVelocity = 0.0;
double motorVmax = 0.0;
double motorAccel = 0.0;
int acoDelay = 0.0; // Offset time for the movement watchdog caused by
// the air cushions in milliseconds.
int axStatus = 0;
/*
The motor record resolution (index motorRecResolution_ in the parameter
library, MRES in the motor record) is NOT a conversion factor between user
units (e.g. mm) and motor units (e.g. encoder steps), but a scaling factor
defining the resolution of the position readback field RRBV. This is due to
an implementation detail inside EPICS described here:
https://epics.anl.gov/tech-talk/2018/msg00089.php
https://github.com/epics-modules/motor/issues/8
Basically, the position value in the parameter library is a double which is
then truncated to an integer in devMotorAsyn.c. Therefore, if we want a
precision of 1 millimeter, we need to set MRES to 1. If we want one of 1
micrometer, we need to set MRES to 0.001. The readback value needs to be
multiplied with MRES to get the actual value.
In the driver, we use user units. Therefore, when we interact with the
parameter library, we need to account for MRES. This means:
- When writing position or speed to the parameter library, we divide the
value by the motor record resolution.
- When reading position or speed from the parameter library, we multiply the
value with the motor record resolution.
Index and motor record field are coupled as follows:
The parameter motorRecResolution_ is coupled to the field MRES of the motor
record in the following manner:
- In sinqMotor.db, the PV (motor_record_pv_name) MOTOR_REC_RESOLUTION
is defined as a copy of the field (motor_record_pv_name).MRES:
record(ao,"$(P)$(M):Resolution") {
field(DESC, "$(M) resolution")
field(DOL, "$(P)$(M).MRES CP MS")
field(OMSL, "closed_loop")
field(DTYP, "asynFloat64")
field(OUT, "@asyn($(PORT),$(ADDR))MOTOR_REC_RESOLUTION")
field(PREC, "$(PREC)")
}
- The PV name MOTOR_REC_RESOLUTION is coupled in asynMotorController.h to
the constant motorRecResolutionString
- ... which in turn is assigned to motorRecResolution_ in
asynMotorController.cpp This way of making the field visible to the driver
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
save the read result to the member variable earlier), since the parameter
library is updated at a later stage!
*/
status = pC_->getDoubleParam(axisNo_, pC_->motorRecResolution_,
&motorRecResolution);
if (status == asynParamUndefined) {
@ -150,21 +98,35 @@ asynStatus pmacV3Axis::readConfig() {
__PRETTY_FUNCTION__, __LINE__);
}
// Software limits and current position
snprintf(command, sizeof(command), "P%2.2d00 Q%2.2d10 Q%2.2d04 Q%2.2d06",
axisNo_, axisNo_, axisNo_, axisNo_);
status = pC_->writeRead(axisNo_, command, response, 4);
nvals = sscanf(response, "%d %lf %lf %lf", &axStatus, &motorPosition,
&motorVelBase, &motorAccel);
if (nvals != 4) {
/*
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_);
status = pC_->writeRead(axisNo_, command, response, 6);
if (status != asynSuccess) {
return status;
}
nvals = sscanf(response, "%d %lf %lf %lf %lf %d", &axStatus, &motorPosition,
&motorVmax, &motorVelocity, &motorAccel, &acoDelay);
// The acoDelay is given in milliseconds -> Convert to seconds, rounded up
offsetMovTimeout_ = std::ceil(acoDelay / 1000.0);
// The PMAC electronic specifies the acceleration in m/s^2. Since we
// otherwise work with the base length mm, the acceleration is converted
// here to mm/s^2.
motorAccel = motorAccel * 1000;
if (nvals != 6) {
return pC_->errMsgCouldNotParseResponse(command, response, axisNo_,
__PRETTY_FUNCTION__, __LINE__);
}
// Transform from motor to parameter library coordinates
motorPosition = motorPosition / motorRecResolution;
motorVelBase = motorVelBase / motorRecResolution;
motorAccel = motorAccel / motorRecResolution;
// Store these values in the parameter library
status = pC_->setDoubleParam(axisNo_, pC_->motorPosition_, motorPosition);
@ -173,24 +135,14 @@ asynStatus pmacV3Axis::readConfig() {
__PRETTY_FUNCTION__, __LINE__);
}
status = pC_->setDoubleParam(axisNo_, pC_->motorVelBase_, motorVelBase);
// Write to the motor record fields
status = setVeloFields(motorVelocity, 0.0, motorVmax);
if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorVelBase_",
__PRETTY_FUNCTION__, __LINE__);
return status;
}
status = pC_->setDoubleParam(axisNo_, pC_->motorAccel_, motorAccel);
status = setAcclField(motorAccel);
if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorAccel_",
__PRETTY_FUNCTION__, __LINE__);
}
// Set the initial enable based on the motor status value
status =
setIntegerParam(pC_->enableMotor_, (axStatus != -3 && axStatus != -5));
if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "enableMotor_",
__PRETTY_FUNCTION__, __LINE__);
return status;
}
// Update the parameter library immediately
@ -210,7 +162,7 @@ asynStatus pmacV3Axis::readConfig() {
}
// Perform the actual poll
asynStatus pmacV3Axis::doPoll(bool *moving) {
asynStatus pmacv3Axis::doPoll(bool *moving) {
// Return value for the poll
asynStatus poll_status = asynSuccess;
@ -234,6 +186,7 @@ asynStatus pmacV3Axis::doPoll(bool *moving) {
int handshakePerformed = 0;
double highLimit = 0.0;
double lowLimit = 0.0;
double limitsOffset = 0.0;
// =========================================================================
@ -287,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
@ -310,20 +263,34 @@ 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.
*/
highLimit = highLimit - offsetLimits_;
lowLimit = lowLimit + offsetLimits_;
pl_status =
pC_->getDoubleParam(axisNo_, pC_->motorLimitsOffset_, &limitsOffset);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorLimitsOffset_",
__PRETTY_FUNCTION__, __LINE__);
}
highLimit = highLimit - limitsOffset;
lowLimit = lowLimit + limitsOffset;
// 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:
@ -445,11 +412,6 @@ asynStatus pmacV3Axis::doPoll(bool *moving) {
}
}
// Check and update the watchdog
if (checkMovTimeoutWatchdog(*moving) != asynSuccess) {
return asynError;
}
// Error handling
switch (error) {
case 0:
@ -559,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);
@ -653,13 +615,6 @@ asynStatus pmacV3Axis::doPoll(bool *moving) {
}
}
pl_status =
setIntegerParam(pC_->motorEnabled_, (axStatus != -3 && axStatus != -5));
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorEnabled_",
__PRETTY_FUNCTION__, __LINE__);
}
pl_status = setIntegerParam(pC_->motorStatusMoving_, *moving);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorStatusMoving_",
@ -693,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);
@ -705,7 +660,7 @@ asynStatus pmacV3Axis::doPoll(bool *moving) {
return poll_status;
}
asynStatus pmacV3Axis::doMove(double position, int relative, double minVelocity,
asynStatus pmacv3Axis::doMove(double position, int relative, double minVelocity,
double maxVelocity, double acceleration) {
// Status of read-write-operations of ASCII commands to the controller
@ -716,14 +671,17 @@ asynStatus pmacV3Axis::doMove(double position, int relative, double minVelocity,
char command[pC_->MAXBUF_], response[pC_->MAXBUF_];
double motorCoordinatesPosition = 0.0;
int enabled = 0;
double motorRecResolution = 0.0;
double motorVelocity = 0.0;
int enabled = 0;
int motorCanSetSpeed = 0;
int writeOffset = 0;
// =========================================================================
pl_status = pC_->getIntegerParam(axisNo_, pC_->motorEnabled_, &enabled);
pl_status = pC_->getIntegerParam(axisNo_, pC_->motorEnableRBV_, &enabled);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorEnabled_",
return pC_->paramLibAccessFailed(pl_status, "enableMotorRBV_",
__PRETTY_FUNCTION__, __LINE__);
}
@ -743,18 +701,43 @@ asynStatus pmacV3Axis::doMove(double position, int relative, double minVelocity,
// Convert from EPICS to user / motor units
motorCoordinatesPosition = position * motorRecResolution;
motorVelocity = maxVelocity * motorRecResolution;
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW,
"%s => line %d:\nStart of axis %d to position %lf.\n",
__PRETTY_FUNCTION__, __LINE__, axisNo_, position);
// Perform handshake, Set target position and start the move command
// Check if the speed is allowed to be changed
pl_status = pC_->getIntegerParam(axisNo_, pC_->motorCanSetSpeed_,
&motorCanSetSpeed);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorCanSetSpeed_",
__PRETTY_FUNCTION__, __LINE__);
}
// Prepend the new motor speed, if the user is allowed to set the speed.
// Mind the " " (space) before the closing "", as the command created here
// is prepended to the one down below.
if (motorCanSetSpeed != 0) {
snprintf(command, sizeof(command), "Q%2.2d04=%lf ", axisNo_,
motorVelocity);
writeOffset = strlen(command);
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW,
"%s => line %d:\nSetting speed of axis %d to %lf.\n",
__PRETTY_FUNCTION__, __LINE__, axisNo_, motorVelocity);
}
// Perform handshake, Set target position (and speed, if allowed) 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_);
snprintf(&command[writeOffset], sizeof(command) - writeOffset,
"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_);
snprintf(&command[writeOffset], sizeof(command) - writeOffset,
"P%2.2d23=0 Q%2.2d01=%lf M%2.2d=1", axisNo_, axisNo_,
motorCoordinatesPosition, axisNo_);
}
// We don't expect an answer
@ -788,7 +771,7 @@ asynStatus pmacV3Axis::doMove(double position, int relative, double minVelocity,
return rw_status;
}
asynStatus pmacV3Axis::stop(double acceleration) {
asynStatus pmacv3Axis::stop(double acceleration) {
// Status of read-write-operations of ASCII commands to the controller
asynStatus rw_status = asynSuccess;
@ -796,42 +779,32 @@ asynStatus pmacV3Axis::stop(double acceleration) {
// Status of parameter library operations
asynStatus pl_status = asynSuccess;
bool moving = false;
char command[pC_->MAXBUF_], response[pC_->MAXBUF_];
// =========================================================================
pl_status = doPoll(&moving);
if (pl_status != asynSuccess) {
return pl_status;
}
snprintf(command, sizeof(command), "M%2.2d=8", axisNo_);
rw_status = pC_->writeRead(axisNo_, command, response, 0);
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, 0);
if (rw_status != asynSuccess) {
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nStopping the movement failed for axis %d.\n",
__PRETTY_FUNCTION__, __LINE__, axisNo_);
if (rw_status != asynSuccess) {
asynPrint(
pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nStopping the movement failed for axis %d.\n",
__PRETTY_FUNCTION__, __LINE__, axisNo_);
pl_status = setIntegerParam(pC_->motorStatusProblem_, true);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status,
"motorStatusProblem_",
__PRETTY_FUNCTION__, __LINE__);
}
pl_status = setIntegerParam(pC_->motorStatusProblem_, true);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorStatusProblem_",
__PRETTY_FUNCTION__, __LINE__);
}
}
return rw_status;
}
/*
Home the axis. On absolute encoder systems, this is a no-op
*/
asynStatus pmacV3Axis::doHome(double min_velocity, double max_velocity,
asynStatus pmacv3Axis::doHome(double min_velocity, double max_velocity,
double acceleration, int forwards) {
// Status of read-write-operations of ASCII commands to the controller
@ -853,6 +826,7 @@ asynStatus pmacV3Axis::doHome(double min_velocity, double max_velocity,
// 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, 0);
if (rw_status != asynSuccess) {
@ -885,7 +859,7 @@ asynStatus pmacV3Axis::doHome(double min_velocity, double max_velocity,
/*
Read the encoder type and update the parameter library accordingly
*/
asynStatus pmacV3Axis::readEncoderType() {
asynStatus pmacv3Axis::readEncoderType() {
// Status of read-write-operations of ASCII commands to the controller
asynStatus rw_status = asynSuccess;
@ -908,12 +882,8 @@ asynStatus pmacV3Axis::readEncoderType() {
int reponse_length = strlen(response);
if (reponse_length < 3) {
asynPrint(
pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nUnexpected reponse '%s' from axis %d on "
"controller %s while reading the encoder type. Aborting....\n",
__PRETTY_FUNCTION__, __LINE__, response, axisNo_, pC_->portName);
return asynError;
return pC_->errMsgCouldNotParseResponse(command, response, axisNo_,
__PRETTY_FUNCTION__, __LINE__);
}
// We are only interested in the last two digits and the last value in
@ -954,7 +924,7 @@ 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.
*/
asynStatus pmacV3Axis::rereadEncoder() {
asynStatus pmacv3Axis::rereadEncoder() {
char command[pC_->MAXBUF_] = {0};
char response[pC_->MAXBUF_] = {0};
char encoderType[pC_->MAXBUF_] = {0};
@ -980,27 +950,20 @@ asynStatus pmacV3Axis::rereadEncoder() {
}
// Abort if the axis is incremental
if (strcmp(encoderType, IncrementalEncoder) == 1) {
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_WARNING,
"%s => line %d:\nTrying to reread absolute encoder of "
"axis %d on controller %s, but it is a relative encoder.\n",
__PRETTY_FUNCTION__, __LINE__, axisNo_, pC_->portName);
pl_status = setStringParam(pC_->motorMessageText_,
"Cannot reread an incremental encoder.");
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorMessageText_",
__PRETTY_FUNCTION__, __LINE__);
}
return asynError;
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;
}
// Check if the axis is disabled. If not, inform the user that this
// is necessary
int enabled = 0;
pl_status = pC_->getIntegerParam(axisNo_, pC_->motorEnabled_, &enabled);
pl_status = pC_->getIntegerParam(axisNo_, pC_->motorEnableRBV_, &enabled);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorEnabled_",
return pC_->paramLibAccessFailed(pl_status, "enableMotorRBV_",
__PRETTY_FUNCTION__, __LINE__);
}
@ -1043,7 +1006,7 @@ asynStatus pmacV3Axis::rereadEncoder() {
return asynSuccess;
}
asynStatus pmacV3Axis::enable(bool on) {
asynStatus pmacv3Axis::enable(bool on) {
int timeout_enable_disable = 2;
char command[pC_->MAXBUF_], response[pC_->MAXBUF_];
@ -1056,14 +1019,17 @@ asynStatus pmacV3Axis::enable(bool on) {
asynStatus pl_status = asynSuccess;
bool moving = false;
doPoll(&moving);
// =========================================================================
// If the axis is currently moving, it cannot be disabled. Ignore the
// command and inform the user
doPoll(&moving); // Muss gehen wenn Status -6
if (moving) {
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_WARNING,
// command and inform the user. We check the last known status of the axis
// instead of "moving", since status -6 is also moving, but the motor can
// actually be disabled in this state!
if (axisStatus_ == 1 || axisStatus_ == 2 || axisStatus_ == 3 ||
axisStatus_ == 4 || axisStatus_ == 5) {
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nAxis %d is not idle and can therefore not "
"be enabled / disabled.\n",
__PRETTY_FUNCTION__, __LINE__, axisNo_);
@ -1076,13 +1042,6 @@ asynStatus pmacV3Axis::enable(bool on) {
__PRETTY_FUNCTION__, __LINE__);
}
// Reset the value in the param lib.
pl_status = setIntegerParam(pC_->enableMotor_, 1);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "enableMotor_",
__PRETTY_FUNCTION__, __LINE__);
}
return asynError;
}
@ -1096,6 +1055,14 @@ asynStatus pmacV3Axis::enable(bool on) {
return asynSuccess;
}
// Reread the encoder, if the axis is going to be enabled
if (on != 0) {
rw_status = rereadEncoder();
if (rw_status != asynSuccess) {
return rw_status;
}
}
// Enable / disable the axis if it is not moving
snprintf(command, sizeof(command), "M%2.2d14=%d", axisNo_, on);
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW,
@ -1111,7 +1078,6 @@ asynStatus pmacV3Axis::enable(bool on) {
return pC_->paramLibAccessFailed(pl_status, "motorMessageText_",
__PRETTY_FUNCTION__, __LINE__);
}
rw_status = pC_->writeRead(axisNo_, command, response, 0);
if (rw_status != asynSuccess) {
return rw_status;

View File

@ -1,33 +1,27 @@
#ifndef pmacV3AXIS_H
#define pmacV3AXIS_H
#ifndef pmacv3AXIS_H
#define pmacv3AXIS_H
#include "sinqAxis.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 pmacV3Controller;
class pmacv3Controller;
class pmacV3Axis : public sinqAxis {
class pmacv3Axis : public sinqAxis {
public:
/**
* @brief Construct a new pmacV3Axis
* @brief Construct a new pmacv3Axis
*
* @param pController Pointer to the associated controller
* @param axisNo Index of the axis
* @param offsetLimit The high and low limits of the axis are read
* out directly from the MCU. However, since the axis might slightly
"overshoot" when moving to a position next to the limits, the MCU might go
into the "limits hit" error state. To prevent this, this value allows adding
a small offset, which is subtracted from the high limit and added to the
low limit.
*/
pmacV3Axis(pmacV3Controller *pController, int axisNo, double offsetLimit);
pmacv3Axis(pmacv3Controller *pController, int axisNo);
/**
* @brief Destroy the pmacV3Axis
* @brief Destroy the pmacv3Axis
*
*/
virtual ~pmacV3Axis();
virtual ~pmacv3Axis();
/**
* @brief Implementation of the `stop` function from asynMotorAxis
@ -110,22 +104,17 @@ class pmacV3Axis : public sinqAxis {
asynStatus rereadEncoder();
protected:
pmacV3Controller *pC_;
pmacv3Controller *pC_;
asynStatus readConfig();
bool initial_poll_;
bool waitForHandshake_;
time_t timeAtHandshake_;
// The axis status is used when enabling / disabling the motor
int axisStatus_;
/*
Stores the constructor input offsetLimits
*/
double offsetLimits_;
private:
friend class pmacV3Controller;
friend class pmacv3Controller;
};
#endif

View File

@ -1,8 +1,7 @@
#include "pmacV3Controller.h"
#include "pmacv3Controller.h"
#include "asynMotorController.h"
#include "asynOctetSyncIO.h"
#include "pmacV3Axis.h"
#include "pmacv3Axis.h"
#include <epicsExport.h>
#include <errlog.h>
#include <iocsh.h>
@ -12,7 +11,25 @@
#include <unistd.h>
/**
* @brief Construct a new pmacV3Controller::pmacV3Controller object
* @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, size_t buf_length) {
for (size_t i = 0; i < buf_length; i++) {
if (src[i] == '\r') {
dst[i] = ' ';
} else {
dst[i] = src[i];
}
}
}
/**
* @brief Construct a new pmacv3Controller::pmacv3Controller object
*
* @param portName See documentation of sinqController
* @param ipPortConfigName See documentation of sinqController
@ -23,7 +40,7 @@
* is declared in writeRead (in seconds)
* @param extraParams See documentation of sinqController
*/
pmacV3Controller::pmacV3Controller(const char *portName,
pmacv3Controller::pmacv3Controller(const char *portName,
const char *ipPortConfigName, int numAxes,
double movingPollPeriod,
double idlePollPeriod, double comTimeout)
@ -31,16 +48,10 @@ pmacV3Controller::pmacV3Controller(const char *portName,
portName, ipPortConfigName, numAxes, movingPollPeriod, idlePollPeriod,
/*
The following parameter library entries are added in this driver:
- ENABLE_AXIS
- AXIS_ENABLED
- ENCODER_TYPE
- REREAD_ENCODER_POSITION
- REREAD_ENCODER_POSITION_RBV
- READ_CONFIG
- MOTOR_HIGH_LIMIT_FROM_DRIVER
- MOTOR_LOW_LIMIT_FROM_DRIVER
*/
8)
NUM_PMACV3_DRIVER_PARAMS)
{
@ -69,33 +80,6 @@ pmacV3Controller::pmacV3Controller(const char *portName,
// =========================================================================
// Create additional parameter library entries
status = createParam("ENABLE_AXIS", asynParamInt32, &enableMotor_);
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("AXIS_ENABLED", asynParamInt32, &motorEnabled_);
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("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) {
@ -106,16 +90,6 @@ pmacV3Controller::pmacV3Controller(const char *portName,
exit(-1);
}
status = createParam("REREAD_ENCODER_POSITION_RBV", asynParamInt32,
&rereadEncoderPositionRBV_);
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("READ_CONFIG", asynParamInt32, &readConfig_);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
@ -125,31 +99,6 @@ pmacV3Controller::pmacV3Controller(const char *portName,
exit(-1);
}
/*
We need to introduce 2 new parameters in order to write the limits from the
driver to the EPICS record. See the comment in pmacV3Controller.h next to
the declaration of motorHighLimitFromDriver_.
*/
status = createParam("MOTOR_HIGH_LIMIT_FROM_DRIVER", asynParamFloat64,
&motorHighLimitFromDriver_);
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("MOTOR_LOW_LIMIT_FROM_DRIVER", asynParamFloat64,
&motorLowLimitFromDriver_);
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);
}
/*
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
@ -187,21 +136,21 @@ 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.
*/
pmacV3Axis *pmacV3Controller::getAxis(asynUser *pasynUser) {
pmacv3Axis *pmacv3Controller::getAxis(asynUser *pasynUser) {
asynMotorAxis *asynAxis = asynMotorController::getAxis(pasynUser);
return pmacV3Controller::castToAxis(asynAxis);
return pmacv3Controller::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
*/
pmacV3Axis *pmacV3Controller::getAxis(int axisNo) {
pmacv3Axis *pmacv3Controller::getAxis(int axisNo) {
asynMotorAxis *asynAxis = asynMotorController::getAxis(axisNo);
return pmacV3Controller::castToAxis(asynAxis);
return pmacv3Controller::castToAxis(asynAxis);
}
pmacV3Axis *pmacV3Controller::castToAxis(asynMotorAxis *asynAxis) {
pmacv3Axis *pmacv3Controller::castToAxis(asynMotorAxis *asynAxis) {
// =========================================================================
@ -212,24 +161,25 @@ pmacV3Axis *pmacV3Controller::castToAxis(asynMotorAxis *asynAxis) {
// Here, an error is emitted since asyn_axis is not a nullptr but also not
// an instance of Axis
pmacV3Axis *axis = dynamic_cast<pmacV3Axis *>(asynAxis);
pmacv3Axis *axis = dynamic_cast<pmacv3Axis *>(asynAxis);
if (axis == nullptr) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nAxis %d is not an instance of pmacV3Axis",
"%s => line %d:\nAxis %d is not an instance of pmacv3Axis",
__PRETTY_FUNCTION__, __LINE__, axis->axisNo_);
}
return axis;
}
asynStatus pmacV3Controller::writeRead(int axisNo, const char *command,
asynStatus pmacv3Controller::writeRead(int axisNo, const char *command,
char *response,
int numExpectedResponses) {
// Definition of local variables.
asynStatus status = asynSuccess;
asynStatus pl_status = asynSuccess;
char full_command[MAXBUF_] = {0};
char user_message[MAXBUF_] = {0};
char fullCommand[MAXBUF_] = {0};
char drvMessageText[MAXBUF_] = {0};
char modResponse[MAXBUF_] = {0};
int motorStatusProblem = 0;
int numReceivedResponses = 0;
@ -247,14 +197,14 @@ asynStatus pmacV3Controller::writeRead(int axisNo, const char *command,
// =========================================================================
pmacV3Axis *axis = getAxis(axisNo);
pmacv3Axis *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
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"
@ -268,7 +218,7 @@ asynStatus pmacV3Controller::writeRead(int axisNo, const char *command,
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
The message has to be build manually into the buffer fullCommand, since it
contains NULL terminators in its middle, therefore the string manipulation
methods of C don't work.
*/
@ -278,20 +228,20 @@ asynStatus pmacV3Controller::writeRead(int axisNo, const char *command,
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
// Positions 2 to 6 must have the value 0. Since fullCommand 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;
fullCommand[0] = '\x40';
fullCommand[1] = '\xBF';
fullCommand[7] = commandLength;
snprintf((char *)full_command + offset, MAXBUF_ - offset, "%s\r", command);
snprintf((char *)fullCommand + offset, MAXBUF_ - offset, "%s\r", command);
asynPrint(this->pasynUserSelf, ASYN_TRACEIO_DRIVER,
"%s => line %d:\nSending command %s", __PRETTY_FUNCTION__,
__LINE__, full_command);
__LINE__, fullCommand);
// Perform the actual writeRead
status = pasynOctetSyncIO->writeRead(
lowLevelPortUser_, full_command, commandLength + offset, response,
lowLevelPortUser_, fullCommand, commandLength + offset, response,
MAXBUF_, comTimeout_, &nbytesOut, &nbytesIn, &eomReason);
/*
@ -327,7 +277,7 @@ asynStatus pmacV3Controller::writeRead(int axisNo, const char *command,
if (status == asynSuccess) {
// If flushing the MCU succeded, try to send the command again
status = pasynOctetSyncIO->writeRead(
lowLevelPortUser_, full_command, commandLength + offset,
lowLevelPortUser_, fullCommand, commandLength + offset,
response, MAXBUF_, comTimeout_, &nbytesOut, &nbytesIn,
&eomReason);
@ -342,15 +292,18 @@ asynStatus pmacV3Controller::writeRead(int axisNo, const char *command,
// Second check: If this fails, give up and propagate the error.
if (numExpectedResponses != numReceivedResponses) {
asynPrint(
this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nUnexpected response %s for command %s\n",
__PRETTY_FUNCTION__, __LINE__, response, command);
snprintf(user_message, sizeof(user_message),
"Received unexpected response %s for command %s. "
adjustResponseForPrint(modResponse, response, MAXBUF_);
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%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' (carriage returns "
"are replaced with spaces) for command %s. "
"Please call the support",
response, command);
pl_status = setStringParam(motorMessageText_, user_message);
modResponse, command);
pl_status = setStringParam(motorMessageText_, drvMessageText);
if (pl_status != asynSuccess) {
return paramLibAccessFailed(pl_status, "motorMessageText_",
__PRETTY_FUNCTION__, __LINE__);
@ -366,34 +319,47 @@ asynStatus pmacV3Controller::writeRead(int axisNo, const char *command,
}
// Create custom error messages for different failure modes
if (strlen(user_message) == 0) {
if (strlen(drvMessageText) == 0) {
switch (status) {
case asynSuccess:
break; // Communicate nothing
case asynTimeout:
snprintf(user_message, sizeof(user_message),
snprintf(drvMessageText, sizeof(drvMessageText),
"connection timeout for axis %d", axisNo);
break;
case asynDisconnected:
snprintf(user_message, sizeof(user_message),
snprintf(drvMessageText, sizeof(drvMessageText),
"axis is not connected");
break;
case asynDisabled:
snprintf(user_message, sizeof(user_message), "axis is disabled");
snprintf(drvMessageText, sizeof(drvMessageText),
"axis is disabled");
break;
default:
snprintf(user_message, sizeof(user_message),
snprintf(drvMessageText, sizeof(drvMessageText),
"Communication failed (%s)", stringifyAsynStatus(status));
break;
}
}
if (status != asynSuccess) {
// Log the overall status (communication successfull or not)
if (status == asynSuccess) {
asynPrint(lowLevelPortUser_, ASYN_TRACEIO_DRIVER,
"%s => line %d:\nDevice response: %s (_ are "
"carriage returns)\n",
__PRETTY_FUNCTION__, __LINE__, modResponse);
pl_status = axis->setIntegerParam(this->motorStatusCommsError_, 0);
} else {
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) {
@ -402,117 +368,68 @@ asynStatus pmacV3Controller::writeRead(int axisNo, const char *command,
}
if (motorStatusProblem == 0) {
pl_status =
axis->setStringParam(this->motorMessageText_, user_message);
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__);
}
}
}
// Log the overall status (communication successfull or not)
if (status == asynSuccess) {
asynPrint(lowLevelPortUser_, ASYN_TRACEIO_DRIVER,
"%s => line %d:\nDevice response: %s\n", __PRETTY_FUNCTION__,
__LINE__, response);
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__, full_command,
stringifyAsynStatus(status));
pl_status = axis->setIntegerParam(this->motorStatusCommsError_, 1);
}
if (pl_status != asynSuccess) {
return paramLibAccessFailed(pl_status, "motorStatusCommsError_",
__PRETTY_FUNCTION__, __LINE__);
}
}
return asynSuccess;
return status;
}
asynStatus pmacV3Controller::writeInt32(asynUser *pasynUser, epicsInt32 value) {
asynPrint(pasynUserSelf, ASYN_TRACE_ERROR, "enabling %d", value);
asynStatus pmacv3Controller::writeInt32(asynUser *pasynUser, epicsInt32 value) {
int function = pasynUser->reason;
pmacV3Axis *axis = getAxis(pasynUser);
// =====================================================================
pmacv3Axis *axis = getAxis(pasynUser);
if (axis == nullptr) {
// We already did the error logging directly in getAxis
return asynError;
}
// Handle custom PVs
if (pasynUser->reason == enableMotor_) {
return axis->enable(value != 0);
} else if (pasynUser->reason == rereadEncoderPosition_) {
if (function == rereadEncoderPosition_) {
return axis->rereadEncoder();
} else if (function == readConfig_) {
return axis->atFirstPoll();
} else {
return asynMotorController::writeInt32(pasynUser, value);
return sinqController::writeInt32(pasynUser, value);
}
}
/*
Overloaded from asynMotorController because the special cases "motor
enabling" and "rereading the encoder" must be covered.
*/
asynStatus pmacV3Controller::readInt32(asynUser *pasynUser, epicsInt32 *value) {
int function = pasynUser->reason;
asynStatus status = asynError;
// =====================================================================
pmacV3Axis *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, "rereadEncoderPosition_",
__PRETTY_FUNCTION__, __LINE__);
}
status =
setIntegerParam(axis->axisNo_, rereadEncoderPositionRBV_, *value);
if (status != asynSuccess) {
return paramLibAccessFailed(status, "rereadEncoderPositionRBV_",
__PRETTY_FUNCTION__, __LINE__);
}
// Update the PVs from the parameter library
return callParamCallbacks();
} else if (function == motorEnabled_) {
char command[MAXBUF_], response[MAXBUF_];
int axStatus = 0;
// Query the motor status from the affected axis
snprintf(command, sizeof(command), "P%2.2d00", axis->axisNo_);
status = writeRead(axis->axisNo_, command, response, 1);
if (status != asynSuccess) {
return status;
}
int nvals = sscanf(response, "%d", &axStatus);
if (nvals != 1) {
return errMsgCouldNotParseResponse(command, response, axis->axisNo_,
__PRETTY_FUNCTION__, __LINE__);
}
// Update the parameter library
return setIntegerParam(motorEnabled_,
(axStatus != -3 && axStatus != -5));
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, MAXBUF_);
return sinqController::errMsgCouldNotParseResponse(
command, modifiedResponse, axisNo, functionName, lineNumber);
}
/*************************************************************************************/
/** The following functions are C-wrappers, and can be called directly from
* iocsh */
@ -520,11 +437,11 @@ asynStatus pmacV3Controller::readInt32(asynUser *pasynUser, epicsInt32 *value) {
extern "C" {
/*
C wrapper for the controller constructor. Please refer to the pmacV3Controller
C wrapper for the controller constructor. Please refer to the pmacv3Controller
constructor documentation.
*/
asynStatus pmacV3CreateController(const char *portName,
const char *lowLevelPortName, int numAxes,
asynStatus pmacv3CreateController(const char *portName,
const char *ipPortConfigName, int numAxes,
double movingPollPeriod,
double idlePollPeriod, double comTimeout) {
/*
@ -538,20 +455,19 @@ 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,
pmacv3Controller *pController =
new pmacv3Controller(portName, ipPortConfigName, numAxes,
movingPollPeriod, idlePollPeriod, comTimeout);
return asynSuccess;
}
/*
C wrapper for the axis constructor. Please refer to the pmacV3Axis constructor
C wrapper for the axis constructor. Please refer to the pmacv3Axis constructor
documentation. The controller is read from the portName.
*/
asynStatus pmacV3CreateAxis(const char *portName, int axis,
double offsetLimits) {
pmacV3Axis *pAxis;
asynStatus pmacv3CreateAxis(const char *portName, int axis) {
pmacv3Axis *pAxis;
/*
findAsynPortDriver is a asyn library FFI function which uses the C ABI.
@ -582,10 +498,10 @@ asynStatus pmacV3CreateAxis(const char *portName, int axis,
asynPortDriver *apd = (asynPortDriver *)(ptr);
// Safe downcast
pmacV3Controller *pC = dynamic_cast<pmacV3Controller *>(apd);
pmacv3Controller *pC = dynamic_cast<pmacv3Controller *>(apd);
if (pC == nullptr) {
errlogPrintf(
"%s => line %d:\ncontroller on port %s is not a pmacV3Controller.",
"%s => line %d:\ncontroller on port %s is not a pmacv3Controller.",
__PRETTY_FUNCTION__, __LINE__, portName);
return asynError;
}
@ -605,7 +521,7 @@ asynStatus pmacV3CreateAxis(const char *portName, int axis,
*/
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-variable"
pAxis = new pmacV3Axis(pC, axis, offsetLimits);
pAxis = new pmacv3Axis(pC, axis);
// Allow manipulation of the controller again
pC->unlock();
@ -627,9 +543,9 @@ 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",
static const iocshArg CreateControllerArg0 = {"Controller name (e.g. mcu1)",
iocshArgString};
static const iocshArg CreateControllerArg1 = {"Low level port name",
static const iocshArg CreateControllerArg1 = {"Asyn IP port name (e.g. pmcu1)",
iocshArgString};
static const iocshArg CreateControllerArg2 = {"Number of axes", iocshArgInt};
static const iocshArg CreateControllerArg3 = {"Moving poll rate (s)",
@ -641,10 +557,10 @@ static const iocshArg CreateControllerArg5 = {"Communication timeout (s)",
static const iocshArg *const CreateControllerArgs[] = {
&CreateControllerArg0, &CreateControllerArg1, &CreateControllerArg2,
&CreateControllerArg3, &CreateControllerArg4, &CreateControllerArg5};
static const iocshFuncDef configPmacV3CreateController = {
"revisedPmacV3CreateController", 6, CreateControllerArgs};
static const iocshFuncDef configPmacV3CreateController = {"pmacv3Controller", 6,
CreateControllerArgs};
static void configPmacV3CreateControllerCallFunc(const iocshArgBuf *args) {
pmacV3CreateController(args[0].sval, args[1].sval, args[2].ival,
pmacv3CreateController(args[0].sval, args[1].sval, args[2].ival,
args[3].dval, args[4].dval, args[5].dval);
}
@ -652,26 +568,25 @@ static void configPmacV3CreateControllerCallFunc(const iocshArgBuf *args) {
Same procedure as for the CreateController function, but for the axis
itself.
*/
static const iocshArg CreateAxisArg0 = {"Controller port name", iocshArgString};
static const iocshArg CreateAxisArg0 = {"Controller name (e.g. mcu1)",
iocshArgString};
static const iocshArg CreateAxisArg1 = {"Axis number", iocshArgInt};
static const iocshArg CreateAxisArg2 = {
"Offset for the MCU limits in mm or degree", iocshArgDouble};
static const iocshArg *const CreateAxisArgs[] = {
&CreateAxisArg0, &CreateAxisArg1, &CreateAxisArg2};
static const iocshFuncDef configPmacV3CreateAxis = {"revisedPmacV3CreateAxis",
3, CreateAxisArgs};
static const iocshArg *const CreateAxisArgs[] = {&CreateAxisArg0,
&CreateAxisArg1};
static const iocshFuncDef configPmacV3CreateAxis = {"pmacv3Axis", 2,
CreateAxisArgs};
static void configPmacV3CreateAxisCallFunc(const iocshArgBuf *args) {
pmacV3CreateAxis(args[0].sval, args[1].ival, args[2].dval);
pmacv3CreateAxis(args[0].sval, args[1].ival);
}
// This function is made known to EPICS in pmacV3.dbd and is called by EPICS
// This function is made known to EPICS in pmacv3.dbd and is called by EPICS
// in order to register both functions in the IOC shell
static void pmacV3Register(void) {
static void pmacv3Register(void) {
iocshRegister(&configPmacV3CreateController,
configPmacV3CreateControllerCallFunc);
iocshRegister(&configPmacV3CreateAxis, configPmacV3CreateAxisCallFunc);
}
epicsExportRegistrar(pmacV3Register);
epicsExportRegistrar(pmacv3Register);
#endif

View File

@ -1,25 +1,22 @@
/********************************************
* pmacV3Controller.h
* pmacv3Controller.h
*
* PMAC V3 controller driver based on the asynMotorController class
*
* Stefan Mathis, September 2024
********************************************/
#ifndef pmacV3Controller_H
#define pmacV3Controller_H
#include "pmacV3Axis.h"
#ifndef pmacv3Controller_H
#define pmacv3Controller_H
#include "pmacv3Axis.h"
#include "sinqAxis.h"
#include "sinqController.h"
#define IncrementalEncoder "Incremental encoder"
#define AbsoluteEncoder "Absolute encoder"
class pmacV3Controller : public sinqController {
class pmacv3Controller : public sinqController {
public:
/**
* @brief Construct a new pmacV3Controller object
* @brief Construct a new pmacv3Controller object
*
* @param portName See sinqController constructor
* @param ipPortConfigName See sinqController constructor
@ -30,7 +27,7 @@ class pmacV3Controller : public sinqController {
the underlying asynOctetSyncIO interface waits for a response until this
time (in seconds) has passed, then it declares a timeout.
*/
pmacV3Controller(const char *portName, const char *ipPortConfigName,
pmacv3Controller(const char *portName, const char *ipPortConfigName,
int numAxes, double movingPollPeriod,
double idlePollPeriod, double comTimeout);
@ -38,23 +35,22 @@ class pmacV3Controller : public sinqController {
* @brief Get the axis object
*
* @param pasynUser Specify the axis via the asynUser
* @return pmacV3Axis* If no axis could be found, this is a nullptr
* @return pmacv3Axis* If no axis could be found, this is a nullptr
*/
pmacV3Axis *getAxis(asynUser *pasynUser);
pmacv3Axis *getAxis(asynUser *pasynUser);
/**
* @brief Get the axis object
*
* @param axisNo Specify the axis via its index
* @return pmacV3Axis* If no axis could be found, this is a nullptr
* @return pmacv3Axis* If no axis could be found, this is a nullptr
*/
pmacV3Axis *getAxis(int axisNo);
pmacv3Axis *getAxis(int axisNo);
/**
* @brief Overloaded function of asynMotorController
* @brief Overloaded function of sinqController
*
* The function is overloaded to allow enabling / disabling the motor and
* rereading the encoder.
* The function is overloaded to allow rereading the encoder and config.
*
* @param pasynUser Specify the axis via the asynUser
* @param value New value
@ -62,18 +58,6 @@ class pmacV3Controller : public sinqController {
*/
asynStatus writeInt32(asynUser *pasynUser, epicsInt32 value);
/**
* @brief Overloaded function of asynMotorController
*
* The function is overloaded to get readback values for the enabling /
* disabling status and the encoder.
*
* @param pasynUser Specify the axis via the asynUser
* @param value Read-out value
* @return asynStatus
*/
asynStatus readInt32(asynUser *pasynUser, epicsInt32 *value);
protected:
asynUser *lowLevelPortUser_;
@ -97,13 +81,36 @@ class pmacV3Controller : public sinqController {
int numExpectedResponses);
/**
* @brief Save cast of the given asynAxis pointer to a pmacV3Axis pointer.
* @brief Save cast of the given asynAxis pointer to a pmacv3Axis pointer.
* If the cast fails, this function returns a nullptr.
*
* @param asynAxis
* @return pmacV3Axis*
* @return pmacv3Axis*
*/
pmacV3Axis *castToAxis(asynMotorAxis *asynAxis);
pmacv3Axis *castToAxis(asynMotorAxis *asynAxis);
/**
* @brief Specialized version of sinqController::errMsgCouldNotParseResponse
* for pmacv3
*
* This is an overloaded version of
* sinqController::errMsgCouldNotParseResponse which calls
* adjustResponseForLogging on response before handing it over to
* sinqController::errMsgCouldNotParseResponse.
*
* @param command Command which led to the unparseable message
* @param response Response which wasn't parseable
* @param axisNo_ Axis where the problem occurred
* @param functionName Name of the caller function. It is recommended
to use a macro, e.g. __func__ or __PRETTY_FUNCTION__.
* @param lineNumber Source code line where this function is
called. It is recommended to use a macro, e.g. __LINE__.
* @return asynStatus Returns asynError.
*/
asynStatus errMsgCouldNotParseResponse(const char *command,
const char *response, int axisNo_,
const char *functionName,
int lineNumber);
private:
// Set the maximum buffer size. This is an empirical value which must be
@ -117,24 +124,13 @@ class pmacV3Controller : public sinqController {
double comTimeout_;
// Indices of additional PVs
int enableMotor_;
int motorEnabled_;
#define FIRST_PMACV3_PARAM rereadEncoderPosition_
int rereadEncoderPosition_;
int rereadEncoderPositionRBV_;
int readConfig_;
int encoderType_;
#define LAST_PMACV3_PARAM readConfig_
/*
These parameters are here to write the high and low limits from the MCU to
the EPICS motor record. Using motorHighLimit_ / motorLowLimit_ does not
work: https://epics.anl.gov/tech-talk/2023/msg00576.php.
Therefore, some additional records are introduced which read from these
parameters and write into the motor record. See the sinq_asyn_motor.db file.
*/
int motorHighLimitFromDriver_;
int motorLowLimitFromDriver_;
friend class pmacV3Axis;
friend class pmacv3Axis;
};
#define NUM_PMACV3_DRIVER_PARAMS (&LAST_PMACV3_PARAM - &FIRST_PMACV3_PARAM + 1)
#endif /* pmacV3Controller_H */
#endif /* pmacv3Controller_H */