Compare commits

...

4 Commits
0.1 ... 0.2.0

Author SHA1 Message Date
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
a325a0b8c7 Merge branch 'fixci' into 'main'
Fix CI Build

See merge request sinq-epics-modules/pmacv3!1
2024-11-20 14:54:34 +01:00
63dba55673 Fix CI Build 2024-11-20 14:54:34 +01:00
9 changed files with 293 additions and 342 deletions

View File

@ -37,14 +37,21 @@ formatting:
build_module:
stage: build
script:
- export SINQMOTOR_VERSION="$(grep 'sinqMotor_VERSION=' Makefile | cut -d= -f2)"
- git clone --depth 1 --branch "${SINQMOTOR_VERSION}" https://gitlab-ci-token:${CI_JOB_TOKEN}@git.psi.ch/sinq-epics-modules/sinqmotor.git
- pushd sinqmotor
- sed -i 's/ARCH_FILTER=.*/ARCH_FILTER=linux%/' Makefile
- echo "LIBVERSION=${SINQMOTOR_VERSION}" >> Makefile
- make install
- popd
- 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.3.0
# 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,15 +1,48 @@
# 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
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.
pmacv3 exposes the following IOC shell functions (all in pmacv3Controller.cpp):
- `pmacv3Controller`: Create a new controller object.
- `pmacv3Axis`: Create a new axis object.
The function arguments are documented directly within the source code or are available from the help function of the IOC shell.
## Database
The pmacV3 module provides additional PVs in the database template db/pmacv3.db. It can be parametrized with the `dbLoadTemplate` function from the IOC shell:
```
require sinqMotor, y.y.y # The sinqMotor module is needed for the pmacv3 module. The version y.y.y is defined in the Makefile (line sinqMotor_VERSION=x.x.x)
require pmacv3, x.x.x # This is the three-digit version number of the pmacv3 module
dbLoadTemplate "motor.substitutions"
```
The substitutions file can be concatenated with that of sinqMotor:
```
file "$(sinqMotor_DB)/sinqMotor.db"
{
pattern
...
}
file "$(pmacv3_DB)/pmacv3.db"
{
pattern
{ AXIS, M}
{ 1, "lin1"}
{ 2, "rot1"}
}
```
The sinqMotor pattern "..." is documented in https://git.psi.ch/sinq-epics-modules/sinqmotor/-/blob/main/README.md.
The other parameters have the following meaning:
- `AXIS`: Index of the axis, corresponds to the physical connection of the axis to the MCU
- `M`: Name of the motor as shown in EPICS
The axis name should correspond to that of the sinqMotor pattern with the same respective index.
## Versioning

59
db/pmacv3.db Normal file
View File

@ -0,0 +1,59 @@
# Encoder type
record(waveform, "$(P)$(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")
}
# reread encoder
record(longout, "$(P)$(M):Reread_Encoder") {
field(DTYP, "asynInt32")
field(OUT, "@asyn($(CONTROLLER),$(AXIS),1) REREAD_ENCODER_POSITION")
field(PINI, "NO")
}
# reread encoder
record(longout, "$(P)$(M):Read_Config") {
field(DTYP, "asynInt32")
field(OUT, "@asyn($(CONTROLLER),$(AXIS),1) READ_CONFIG")
field(PINI, "NO")
}
# ===================================================================
# The following records read acceleration and velocity from the driver and
# copy those values into the corresponding fields of the main motor record.
# This strategy is described here: https://epics.anl.gov/tech-talk/2022/msg00464.php
# Helper record for the high limit which is filled in by the driver
record(ai, "$(P)$(M):MOTOR_VELOCITY-RBV")
{
field(DTYP, "asynFloat64")
field(INP, "@asyn($(CONTROLLER),$(AXIS)) MOTOR_VELOCITY_FROM_DRIVER")
field(SCAN, "I/O Intr")
field(FLNK, "$(P)$(M):PUSH_VELO_TO_FIELD")
}
# Push the value into the field of the main motor record
record(ao, "$(P)$(M):PUSH_VELO_TO_FIELD") {
field(DOL, "$(P)$(M):MOTOR_VELOCITY-RBV CP")
field(OUT, "$(P)$(M).VELO")
field(OMSL, "closed_loop") # This configuration keeps the PV and the field in sync
}
# Helper record for the low limit which is filled in by the driver
record(ai, "$(P)$(M):MOTOR_ACCL-RBV")
{
field(DTYP, "asynFloat64")
field(INP, "@asyn($(CONTROLLER),$(AXIS)) MOTOR_ACCEL_FROM_DRIVER")
field(SCAN, "I/O Intr")
field(FLNK, "$(P)$(M):PUSH_ACCL_TO_FIELD")
}
# Push the value into the field of the main motor record
record(ao, "$(P)$(M):PUSH_ACCL_TO_FIELD") {
field(DOL, "$(P)$(M):MOTOR_ACCL-RBV CP")
field(OUT, "$(P)$(M).ACCL")
field(OMSL, "closed_loop") # This configuration keeps the PV and the field in sync
}

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);
@ -66,7 +65,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 +73,12 @@ pmacV3Axis::~pmacV3Axis(void) {
/**
Read the configuration at the first poll
*/
asynStatus pmacV3Axis::atFirstPoll() { return readConfig(); }
asynStatus pmacv3Axis::atFirstPoll() { return readConfig(); }
/*
Read the configuration from the motor control unit and the parameter library.
*/
asynStatus pmacV3Axis::readConfig() {
asynStatus pmacv3Axis::readConfig() {
// Local variable declaration
asynStatus status = asynSuccess;
@ -87,60 +86,12 @@ asynStatus pmacV3Axis::readConfig() {
int nvals = 0;
double motorRecResolution = 0.0;
double motorPosition = 0.0;
double motorVelBase = 0.0;
double motorVelocity = 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) {
@ -151,20 +102,31 @@ asynStatus pmacV3Axis::readConfig() {
}
// 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) {
snprintf(command, sizeof(command),
"P%2.2d00 Q%2.2d10 Q%2.2d04 Q%2.2d06 P%2.2d22", axisNo_, axisNo_,
axisNo_, axisNo_, axisNo_);
status = pC_->writeRead(axisNo_, command, response, 5);
if (status != asynSuccess) {
return status;
}
nvals = sscanf(response, "%d %lf %lf %lf %d", &axStatus, &motorPosition,
&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 != 5) {
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,15 +135,16 @@ asynStatus pmacV3Axis::readConfig() {
__PRETTY_FUNCTION__, __LINE__);
}
status = pC_->setDoubleParam(axisNo_, pC_->motorVelBase_, motorVelBase);
status =
pC_->setDoubleParam(axisNo_, pC_->motorVelocityRBV_, motorVelocity);
if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorVelBase_",
return pC_->paramLibAccessFailed(status, "motorVelocityRBV_",
__PRETTY_FUNCTION__, __LINE__);
}
status = pC_->setDoubleParam(axisNo_, pC_->motorAccel_, motorAccel);
status = pC_->setDoubleParam(axisNo_, pC_->motorAccelRBV_, motorAccel);
if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorAccel_",
return pC_->paramLibAccessFailed(status, "motorAccelRBV_",
__PRETTY_FUNCTION__, __LINE__);
}
@ -210,7 +173,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 +197,7 @@ asynStatus pmacV3Axis::doPoll(bool *moving) {
int handshakePerformed = 0;
double highLimit = 0.0;
double lowLimit = 0.0;
double limitsOffset = 0.0;
// =========================================================================
@ -287,7 +251,7 @@ asynStatus pmacV3Axis::doPoll(bool *moving) {
}
// Transform from EPICS to motor coordinates (see comment in
// pmacV3Axis::readConfig())
// pmacv3Axis::readConfig())
previousPosition = previousPosition * motorRecResolution;
// Query the axis status
@ -318,8 +282,14 @@ asynStatus pmacV3Axis::doPoll(bool *moving) {
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 - 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;
@ -445,11 +415,6 @@ asynStatus pmacV3Axis::doPoll(bool *moving) {
}
}
// Check and update the watchdog
if (checkMovTimeoutWatchdog(*moving) != asynSuccess) {
return asynError;
}
// Error handling
switch (error) {
case 0:
@ -653,10 +618,10 @@ asynStatus pmacV3Axis::doPoll(bool *moving) {
}
}
pl_status =
setIntegerParam(pC_->motorEnabled_, (axStatus != -3 && axStatus != -5));
pl_status = setIntegerParam(pC_->enableMotorRBV_,
(axStatus != -3 && axStatus != -5));
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorEnabled_",
return pC_->paramLibAccessFailed(pl_status, "enableMotorRBV_",
__PRETTY_FUNCTION__, __LINE__);
}
@ -693,7 +658,7 @@ asynStatus pmacV3Axis::doPoll(bool *moving) {
}
// Transform from motor to EPICS coordinates (see comment in
// pmacV3Axis::readConfig())
// pmacv3Axis::readConfig())
currentPosition = currentPosition / motorRecResolution;
pl_status = setDoubleParam(pC_->motorPosition_, currentPosition);
@ -705,7 +670,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
@ -721,9 +686,9 @@ asynStatus pmacV3Axis::doMove(double position, int relative, double minVelocity,
// =========================================================================
pl_status = pC_->getIntegerParam(axisNo_, pC_->motorEnabled_, &enabled);
pl_status = pC_->getIntegerParam(axisNo_, pC_->enableMotorRBV_, &enabled);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorEnabled_",
return pC_->paramLibAccessFailed(pl_status, "enableMotorRBV_",
__PRETTY_FUNCTION__, __LINE__);
}
@ -788,7 +753,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 +761,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 +808,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 +841,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;
@ -954,7 +910,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};
@ -998,9 +954,9 @@ asynStatus pmacV3Axis::rereadEncoder() {
// 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_->enableMotorRBV_, &enabled);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorEnabled_",
return pC_->paramLibAccessFailed(pl_status, "enableMotorRBV_",
__PRETTY_FUNCTION__, __LINE__);
}
@ -1043,7 +999,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 +1012,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_);
@ -1161,3 +1120,8 @@ asynStatus pmacV3Axis::enable(bool on) {
}
return asynError;
}
asynStatus pmacv3Axis::isEnabled(bool *on) {
*on = (axisStatus_ != -3 && axisStatus_ != -5);
return asynSuccess;
}

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
@ -94,6 +88,15 @@ 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.
@ -110,7 +113,7 @@ class pmacV3Axis : public sinqAxis {
asynStatus rereadEncoder();
protected:
pmacV3Controller *pC_;
pmacv3Controller *pC_;
asynStatus readConfig();
bool initial_poll_;
@ -119,13 +122,9 @@ class pmacV3Axis : public sinqAxis {
// 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,8 @@
#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 +12,7 @@
#include <unistd.h>
/**
* @brief Construct a new pmacV3Controller::pmacV3Controller object
* @brief Construct a new pmacv3Controller::pmacv3Controller object
*
* @param portName See documentation of sinqController
* @param ipPortConfigName See documentation of sinqController
@ -23,7 +23,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 +31,13 @@ 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
- MOTOR_VELOCITY_FROM_DRIVER
- MOTOR_ACCEL_FROM_DRIVER
*/
8)
5)
{
@ -69,24 +66,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,
@ -106,16 +85,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,13 +94,8 @@ 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_);
status = createParam("MOTOR_VELOCITY_FROM_DRIVER", asynParamFloat64,
&motorVelocityRBV_);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (creating a parameter failed "
@ -140,8 +104,8 @@ pmacV3Controller::pmacV3Controller(const char *portName,
exit(-1);
}
status = createParam("MOTOR_LOW_LIMIT_FROM_DRIVER", asynParamFloat64,
&motorLowLimitFromDriver_);
status = createParam("MOTOR_ACCEL_FROM_DRIVER", asynParamFloat64,
&motorAccelRBV_);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (creating a parameter failed "
@ -187,21 +151,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,16 +176,16 @@ 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) {
@ -247,14 +211,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"
@ -435,81 +399,24 @@ asynStatus pmacV3Controller::writeRead(int axisNo, const char *command,
return asynSuccess;
}
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->readConfig();
} else {
return asynMotorController::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));
} else {
return asynMotorController::readInt32(pasynUser, value);
return sinqController::writeInt32(pasynUser, value);
}
}
@ -520,10 +427,10 @@ 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,
asynStatus pmacv3CreateController(const char *portName,
const char *lowLevelPortName, int numAxes,
double movingPollPeriod,
double idlePollPeriod, double comTimeout) {
@ -538,20 +445,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, lowLevelPortName, 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 +488,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 +511,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();
@ -641,10 +547,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);
}
@ -654,24 +560,22 @@ itself.
*/
static const iocshArg CreateAxisArg0 = {"Controller port name", 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,25 @@
/********************************************
* 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 +30,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,17 +38,17 @@ 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
@ -62,18 +62,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 +85,13 @@ 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);
private:
// Set the maximum buffer size. This is an empirical value which must be
@ -117,24 +105,18 @@ class pmacV3Controller : public sinqController {
double comTimeout_;
// Indices of additional PVs
int enableMotor_;
int motorEnabled_;
int rereadEncoderPosition_;
int rereadEncoderPositionRBV_;
int readConfig_;
int encoderType_;
/*
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_;
Same strategy as with the limits in sinqController -> Use additional PVs to
write speed and acceleration from the driver to the record.
*/
int motorVelocityRBV_;
int motorAccelRBV_;
friend class pmacV3Axis;
friend class pmacv3Axis;
};
#endif /* pmacV3Controller_H */
#endif /* pmacv3Controller_H */