Compare commits

...

6 Commits
0.2.0 ... 0.3.0

6 changed files with 196 additions and 202 deletions

View File

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

View File

@ -4,50 +4,39 @@
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. 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
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.
## Developer guide
### Usage in IOC shell
pmacv3 exposes the following IOC shell functions (all in pmacv3Controller.cpp): pmacv3 exposes the following IOC shell functions (all in pmacv3Controller.cpp):
- `pmacv3Controller`: Create a new controller object. - `pmacv3Controller`: Create a new controller object.
- `pmacv3Axis`: Create a new axis 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. These functions are parametrized as follows:
## 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) pmacv3Controller(
require pmacv3, x.x.x # This is the three-digit version number of the pmacv3 module "$(NAME)", # Name of the MCU, e.g. mcu1. This parameter should be provided by an environment variable.
dbLoadTemplate "motor.substitutions" "$(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.
);
``` ```
The substitutions file can be concatenated with that of sinqMotor: ### Versioning
```
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
Please see the documentation for the module sinqMotor: https://git.psi.ch/sinq-epics-modules/sinqmotor/-/blob/main/README.md. 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. Please see the documentation for the module sinqMotor: https://git.psi.ch/sinq-epics-modules/sinqmotor/-/blob/main/README.md.

View File

@ -1,5 +1,7 @@
# Encoder type # Read out the encoder type in human-readable form. The output numbers can be
record(waveform, "$(P)$(M):Encoder_Type") { # interpreted as ASCII.
# This record is coupled to the parameter library via encoderType -> ENCODER_TYPE.
record(waveform, "$(INSTR)$(M):Encoder_Type") {
field(DTYP, "asynOctetRead") field(DTYP, "asynOctetRead")
field(INP, "@asyn($(CONTROLLER),$(AXIS),1) ENCODER_TYPE") field(INP, "@asyn($(CONTROLLER),$(AXIS),1) ENCODER_TYPE")
field(FTVL, "CHAR") field(FTVL, "CHAR")
@ -7,53 +9,22 @@ record(waveform, "$(P)$(M):Encoder_Type") {
field(SCAN, "I/O Intr") field(SCAN, "I/O Intr")
} }
# reread encoder # Trigger a rereading of the encoder. This action is sometimes necessary for
record(longout, "$(P)$(M):Reread_Encoder") { # absolute encoders after enabling them. For incremental encoders, this is a no-op.
# This record is coupled to the parameter library via rereadEncoderPosition_ -> REREAD_ENCODER_POSITION.
record(longout, "$(INSTR)$(M):Reread_Encoder") {
field(DTYP, "asynInt32") field(DTYP, "asynInt32")
field(OUT, "@asyn($(CONTROLLER),$(AXIS),1) REREAD_ENCODER_POSITION") field(OUT, "@asyn($(CONTROLLER),$(AXIS),1) REREAD_ENCODER_POSITION")
field(PINI, "NO") field(PINI, "NO")
} }
# reread encoder # The pmacV3 driver reads certain configuration parameters (such as the velocity
record(longout, "$(P)$(M):Read_Config") { # 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):Read_Config") {
field(DTYP, "asynInt32") field(DTYP, "asynInt32")
field(OUT, "@asyn($(CONTROLLER),$(AXIS),1) READ_CONFIG") field(OUT, "@asyn($(CONTROLLER),$(AXIS),1) READ_CONFIG")
field(PINI, "NO") 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

@ -87,6 +87,7 @@ asynStatus pmacv3Axis::readConfig() {
double motorRecResolution = 0.0; double motorRecResolution = 0.0;
double motorPosition = 0.0; double motorPosition = 0.0;
double motorVelocity = 0.0; double motorVelocity = 0.0;
double motorVmax = 0.0;
double motorAccel = 0.0; double motorAccel = 0.0;
int acoDelay = 0.0; // Offset time for the movement watchdog caused by int acoDelay = 0.0; // Offset time for the movement watchdog caused by
// the air cushions in milliseconds. // the air cushions in milliseconds.
@ -103,14 +104,14 @@ asynStatus pmacv3Axis::readConfig() {
// Software limits and current position // Software limits and current position
snprintf(command, sizeof(command), snprintf(command, sizeof(command),
"P%2.2d00 Q%2.2d10 Q%2.2d04 Q%2.2d06 P%2.2d22", axisNo_, axisNo_, "P%2.2d00 Q%2.2d10 Q%2.2d03 Q%2.2d04 Q%2.2d06 P%2.2d22", axisNo_,
axisNo_, axisNo_, axisNo_); axisNo_, axisNo_, axisNo_, axisNo_, axisNo_);
status = pC_->writeRead(axisNo_, command, response, 5); status = pC_->writeRead(axisNo_, command, response, 6);
if (status != asynSuccess) { if (status != asynSuccess) {
return status; return status;
} }
nvals = sscanf(response, "%d %lf %lf %lf %d", &axStatus, &motorPosition, nvals = sscanf(response, "%d %lf %lf %lf %lf %d", &axStatus, &motorPosition,
&motorVelocity, &motorAccel, &acoDelay); &motorVmax, &motorVelocity, &motorAccel, &acoDelay);
// The acoDelay is given in milliseconds -> Convert to seconds, rounded up // The acoDelay is given in milliseconds -> Convert to seconds, rounded up
offsetMovTimeout_ = std::ceil(acoDelay / 1000.0); offsetMovTimeout_ = std::ceil(acoDelay / 1000.0);
@ -120,7 +121,7 @@ asynStatus pmacv3Axis::readConfig() {
// here to mm/s^2. // here to mm/s^2.
motorAccel = motorAccel * 1000; motorAccel = motorAccel * 1000;
if (nvals != 5) { if (nvals != 6) {
return pC_->errMsgCouldNotParseResponse(command, response, axisNo_, return pC_->errMsgCouldNotParseResponse(command, response, axisNo_,
__PRETTY_FUNCTION__, __LINE__); __PRETTY_FUNCTION__, __LINE__);
} }
@ -135,25 +136,14 @@ asynStatus pmacv3Axis::readConfig() {
__PRETTY_FUNCTION__, __LINE__); __PRETTY_FUNCTION__, __LINE__);
} }
status = // Write to the motor record fields
pC_->setDoubleParam(axisNo_, pC_->motorVelocityRBV_, motorVelocity); status = setVeloFields(motorVelocity, 0.0, motorVmax);
if (status != asynSuccess) { if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorVelocityRBV_", return status;
__PRETTY_FUNCTION__, __LINE__);
} }
status = setAcclField(motorAccel);
status = pC_->setDoubleParam(axisNo_, pC_->motorAccelRBV_, motorAccel);
if (status != asynSuccess) { if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorAccelRBV_", return status;
__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__);
} }
// Update the parameter library immediately // Update the parameter library immediately
@ -618,10 +608,10 @@ asynStatus pmacv3Axis::doPoll(bool *moving) {
} }
} }
pl_status = setIntegerParam(pC_->enableMotorRBV_, pl_status = setIntegerParam(pC_->motorEnableRBV_,
(axStatus != -3 && axStatus != -5)); (axStatus != -3 && axStatus != -5));
if (pl_status != asynSuccess) { if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "enableMotorRBV_", return pC_->paramLibAccessFailed(pl_status, "motorEnableRBV_",
__PRETTY_FUNCTION__, __LINE__); __PRETTY_FUNCTION__, __LINE__);
} }
@ -681,12 +671,15 @@ asynStatus pmacv3Axis::doMove(double position, int relative, double minVelocity,
char command[pC_->MAXBUF_], response[pC_->MAXBUF_]; char command[pC_->MAXBUF_], response[pC_->MAXBUF_];
double motorCoordinatesPosition = 0.0; double motorCoordinatesPosition = 0.0;
int enabled = 0;
double motorRecResolution = 0.0; double motorRecResolution = 0.0;
double motorVelocity = 0.0;
int enabled = 0;
int motorCanSetSpeed = 0;
int writeOffset = 0;
// ========================================================================= // =========================================================================
pl_status = pC_->getIntegerParam(axisNo_, pC_->enableMotorRBV_, &enabled); pl_status = pC_->getIntegerParam(axisNo_, pC_->motorEnableRBV_, &enabled);
if (pl_status != asynSuccess) { if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "enableMotorRBV_", return pC_->paramLibAccessFailed(pl_status, "enableMotorRBV_",
__PRETTY_FUNCTION__, __LINE__); __PRETTY_FUNCTION__, __LINE__);
@ -708,18 +701,43 @@ asynStatus pmacv3Axis::doMove(double position, int relative, double minVelocity,
// Convert from EPICS to user / motor units // Convert from EPICS to user / motor units
motorCoordinatesPosition = position * motorRecResolution; motorCoordinatesPosition = position * motorRecResolution;
motorVelocity = maxVelocity * motorRecResolution;
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW, asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW,
"%s => line %d:\nStart of axis %d to position %lf.\n", "%s => line %d:\nStart of axis %d to position %lf.\n",
__PRETTY_FUNCTION__, __LINE__, axisNo_, position); __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) { if (relative) {
snprintf(command, sizeof(command), "P%2.2d23=0 Q%2.2d02=%lf M%2.2d=2", snprintf(&command[writeOffset], sizeof(command) - writeOffset,
axisNo_, axisNo_, motorCoordinatesPosition, axisNo_); "P%2.2d23=0 Q%2.2d02=%lf M%2.2d=2", axisNo_, axisNo_,
motorCoordinatesPosition, axisNo_);
} else { } else {
snprintf(command, sizeof(command), "P%2.2d23=0 Q%2.2d01=%lf M%2.2d=1", snprintf(&command[writeOffset], sizeof(command) - writeOffset,
axisNo_, axisNo_, motorCoordinatesPosition, axisNo_); "P%2.2d23=0 Q%2.2d01=%lf M%2.2d=1", axisNo_, axisNo_,
motorCoordinatesPosition, axisNo_);
} }
// We don't expect an answer // We don't expect an answer
@ -864,12 +882,8 @@ asynStatus pmacv3Axis::readEncoderType() {
int reponse_length = strlen(response); int reponse_length = strlen(response);
if (reponse_length < 3) { if (reponse_length < 3) {
asynPrint( return pC_->errMsgCouldNotParseResponse(command, response, axisNo_,
pC_->pasynUserSelf, ASYN_TRACE_ERROR, __PRETTY_FUNCTION__, __LINE__);
"%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;
} }
// We are only interested in the last two digits and the last value in // We are only interested in the last two digits and the last value in
@ -937,24 +951,13 @@ asynStatus pmacv3Axis::rereadEncoder() {
// Abort if the axis is incremental // Abort if the axis is incremental
if (strcmp(encoderType, IncrementalEncoder) == 1) { if (strcmp(encoderType, IncrementalEncoder) == 1) {
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_WARNING, return asynSuccess;
"%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;
} }
// Check if the axis is disabled. If not, inform the user that this // Check if the axis is disabled. If not, inform the user that this
// is necessary // is necessary
int enabled = 0; int enabled = 0;
pl_status = pC_->getIntegerParam(axisNo_, pC_->enableMotorRBV_, &enabled); pl_status = pC_->getIntegerParam(axisNo_, pC_->motorEnableRBV_, &enabled);
if (pl_status != asynSuccess) { if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "enableMotorRBV_", return pC_->paramLibAccessFailed(pl_status, "enableMotorRBV_",
__PRETTY_FUNCTION__, __LINE__); __PRETTY_FUNCTION__, __LINE__);
@ -1035,13 +1038,6 @@ asynStatus pmacv3Axis::enable(bool on) {
__PRETTY_FUNCTION__, __LINE__); __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; return asynError;
} }
@ -1055,6 +1051,14 @@ asynStatus pmacv3Axis::enable(bool on) {
return asynSuccess; 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 // Enable / disable the axis if it is not moving
snprintf(command, sizeof(command), "M%2.2d14=%d", axisNo_, on); snprintf(command, sizeof(command), "M%2.2d14=%d", axisNo_, on);
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW, asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW,
@ -1070,7 +1074,6 @@ asynStatus pmacv3Axis::enable(bool on) {
return pC_->paramLibAccessFailed(pl_status, "motorMessageText_", return pC_->paramLibAccessFailed(pl_status, "motorMessageText_",
__PRETTY_FUNCTION__, __LINE__); __PRETTY_FUNCTION__, __LINE__);
} }
rw_status = pC_->writeRead(axisNo_, command, response, 0); rw_status = pC_->writeRead(axisNo_, command, response, 0);
if (rw_status != asynSuccess) { if (rw_status != asynSuccess) {
return rw_status; return rw_status;

View File

@ -1,3 +1,5 @@
// Needed to use strcpy_s from string.h
#define __STDC_WANT_LIB_EXT1__ 1
#include "pmacv3Controller.h" #include "pmacv3Controller.h"
#include "asynMotorController.h" #include "asynMotorController.h"
@ -11,6 +13,24 @@
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
/**
* @brief Copy src into dst and replace all carriage returns with spaces
*
* @param dst Buffer for the modified string
* @param src Original string
*/
void adjustResponseForPrint(char *dst, const char *src) {
// Needed to use strcpy_s from string.h
#ifdef __STDC_LIB_EXT1__
strcpy_s(dst, src);
for (size_t i = 0; i < strlen(dst); i++) {
if (dst[i] == '\r') {
dst[i] = '_';
}
}
#endif
}
/** /**
* @brief Construct a new pmacv3Controller::pmacv3Controller object * @brief Construct a new pmacv3Controller::pmacv3Controller object
* *
@ -34,10 +54,9 @@ pmacv3Controller::pmacv3Controller(const char *portName,
- ENCODER_TYPE - ENCODER_TYPE
- REREAD_ENCODER_POSITION - REREAD_ENCODER_POSITION
- READ_CONFIG - READ_CONFIG
- MOTOR_VELOCITY_FROM_DRIVER - ACCEL_FROM_DRIVER
- MOTOR_ACCEL_FROM_DRIVER
*/ */
5) 4)
{ {
@ -94,26 +113,6 @@ pmacv3Controller::pmacv3Controller(const char *portName,
exit(-1); exit(-1);
} }
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 "
"with %s).\nTerminating IOC",
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status));
exit(-1);
}
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 "
"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. 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 It is not necessary to append a terminator to outgoing messages, since
@ -192,8 +191,9 @@ asynStatus pmacv3Controller::writeRead(int axisNo, const char *command,
// Definition of local variables. // Definition of local variables.
asynStatus status = asynSuccess; asynStatus status = asynSuccess;
asynStatus pl_status = asynSuccess; asynStatus pl_status = asynSuccess;
char full_command[MAXBUF_] = {0}; char fullCommand[MAXBUF_] = {0};
char user_message[MAXBUF_] = {0}; char drvMessageText[MAXBUF_] = {0};
char modResponse[MAXBUF_] = {0};
int motorStatusProblem = 0; int motorStatusProblem = 0;
int numReceivedResponses = 0; int numReceivedResponses = 0;
@ -232,7 +232,7 @@ asynStatus pmacv3Controller::writeRead(int axisNo, const char *command,
this protocol encodes the message length at the beginning. See Turbo PMAC this protocol encodes the message length at the beginning. See Turbo PMAC
User Manual, page 418 in VR_PMAC_GETRESPONSE 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 contains NULL terminators in its middle, therefore the string manipulation
methods of C don't work. methods of C don't work.
*/ */
@ -242,20 +242,20 @@ asynStatus pmacv3Controller::writeRead(int axisNo, const char *command,
strlen(command) + 1; // +1 because of the appended /r strlen(command) + 1; // +1 because of the appended /r
const int offset = 8; 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. // as an array of zeros, we don't need to set these bits manually.
full_command[0] = '\x40'; fullCommand[0] = '\x40';
full_command[1] = '\xBF'; fullCommand[1] = '\xBF';
full_command[7] = commandLength; 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, asynPrint(this->pasynUserSelf, ASYN_TRACEIO_DRIVER,
"%s => line %d:\nSending command %s", __PRETTY_FUNCTION__, "%s => line %d:\nSending command %s", __PRETTY_FUNCTION__,
__LINE__, full_command); __LINE__, fullCommand);
// Perform the actual writeRead // Perform the actual writeRead
status = pasynOctetSyncIO->writeRead( status = pasynOctetSyncIO->writeRead(
lowLevelPortUser_, full_command, commandLength + offset, response, lowLevelPortUser_, fullCommand, commandLength + offset, response,
MAXBUF_, comTimeout_, &nbytesOut, &nbytesIn, &eomReason); MAXBUF_, comTimeout_, &nbytesOut, &nbytesIn, &eomReason);
/* /*
@ -291,7 +291,7 @@ asynStatus pmacv3Controller::writeRead(int axisNo, const char *command,
if (status == asynSuccess) { if (status == asynSuccess) {
// If flushing the MCU succeded, try to send the command again // If flushing the MCU succeded, try to send the command again
status = pasynOctetSyncIO->writeRead( status = pasynOctetSyncIO->writeRead(
lowLevelPortUser_, full_command, commandLength + offset, lowLevelPortUser_, fullCommand, commandLength + offset,
response, MAXBUF_, comTimeout_, &nbytesOut, &nbytesIn, response, MAXBUF_, comTimeout_, &nbytesOut, &nbytesIn,
&eomReason); &eomReason);
@ -306,15 +306,18 @@ asynStatus pmacv3Controller::writeRead(int axisNo, const char *command,
// Second check: If this fails, give up and propagate the error. // Second check: If this fails, give up and propagate the error.
if (numExpectedResponses != numReceivedResponses) { if (numExpectedResponses != numReceivedResponses) {
asynPrint(
this->pasynUserSelf, ASYN_TRACE_ERROR, adjustResponseForPrint(modResponse, response);
"%s => line %d:\nUnexpected response %s for command %s\n", asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
__PRETTY_FUNCTION__, __LINE__, response, command); "%s => line %d:\nUnexpected response %s (_ are "
snprintf(user_message, sizeof(user_message), "carriage returns) for command %s\n",
"Received unexpected response %s for command %s. " __PRETTY_FUNCTION__, __LINE__, modResponse, command);
snprintf(drvMessageText, sizeof(drvMessageText),
"Received unexpected response %s (_ are "
"carriage returns) for command %s. "
"Please call the support", "Please call the support",
response, command); modResponse, command);
pl_status = setStringParam(motorMessageText_, user_message); pl_status = setStringParam(motorMessageText_, drvMessageText);
if (pl_status != asynSuccess) { if (pl_status != asynSuccess) {
return paramLibAccessFailed(pl_status, "motorMessageText_", return paramLibAccessFailed(pl_status, "motorMessageText_",
__PRETTY_FUNCTION__, __LINE__); __PRETTY_FUNCTION__, __LINE__);
@ -330,23 +333,24 @@ asynStatus pmacv3Controller::writeRead(int axisNo, const char *command,
} }
// Create custom error messages for different failure modes // Create custom error messages for different failure modes
if (strlen(user_message) == 0) { if (strlen(drvMessageText) == 0) {
switch (status) { switch (status) {
case asynSuccess: case asynSuccess:
break; // Communicate nothing break; // Communicate nothing
case asynTimeout: case asynTimeout:
snprintf(user_message, sizeof(user_message), snprintf(drvMessageText, sizeof(drvMessageText),
"connection timeout for axis %d", axisNo); "connection timeout for axis %d", axisNo);
break; break;
case asynDisconnected: case asynDisconnected:
snprintf(user_message, sizeof(user_message), snprintf(drvMessageText, sizeof(drvMessageText),
"axis is not connected"); "axis is not connected");
break; break;
case asynDisabled: case asynDisabled:
snprintf(user_message, sizeof(user_message), "axis is disabled"); snprintf(drvMessageText, sizeof(drvMessageText),
"axis is disabled");
break; break;
default: default:
snprintf(user_message, sizeof(user_message), snprintf(drvMessageText, sizeof(drvMessageText),
"Communication failed (%s)", stringifyAsynStatus(status)); "Communication failed (%s)", stringifyAsynStatus(status));
break; break;
} }
@ -367,7 +371,7 @@ asynStatus pmacv3Controller::writeRead(int axisNo, const char *command,
if (motorStatusProblem == 0) { if (motorStatusProblem == 0) {
pl_status = pl_status =
axis->setStringParam(this->motorMessageText_, user_message); axis->setStringParam(this->motorMessageText_, drvMessageText);
if (pl_status != asynSuccess) { if (pl_status != asynSuccess) {
return paramLibAccessFailed(pl_status, "motorMessageText_", return paramLibAccessFailed(pl_status, "motorMessageText_",
__PRETTY_FUNCTION__, __LINE__); __PRETTY_FUNCTION__, __LINE__);
@ -377,16 +381,18 @@ asynStatus pmacv3Controller::writeRead(int axisNo, const char *command,
// Log the overall status (communication successfull or not) // Log the overall status (communication successfull or not)
if (status == asynSuccess) { if (status == asynSuccess) {
asynPrint(lowLevelPortUser_, ASYN_TRACEIO_DRIVER, asynPrint(lowLevelPortUser_, ASYN_TRACEIO_DRIVER,
"%s => line %d:\nDevice response: %s\n", __PRETTY_FUNCTION__, "%s => line %d:\nDevice response: %s (_ are "
__LINE__, response); "carriage returns)\n",
__PRETTY_FUNCTION__, __LINE__, modResponse);
pl_status = axis->setIntegerParam(this->motorStatusCommsError_, 0); pl_status = axis->setIntegerParam(this->motorStatusCommsError_, 0);
} else { } else {
if (status == asynSuccess) { if (status == asynSuccess) {
asynPrint( asynPrint(
lowLevelPortUser_, ASYN_TRACE_ERROR, lowLevelPortUser_, ASYN_TRACE_ERROR,
"%s => line %d:\nCommunication failed for command %s (%s)\n", "%s => line %d:\nCommunication failed for command %s (%s)\n",
__PRETTY_FUNCTION__, __LINE__, full_command, __PRETTY_FUNCTION__, __LINE__, fullCommand,
stringifyAsynStatus(status)); stringifyAsynStatus(status));
pl_status = axis->setIntegerParam(this->motorStatusCommsError_, 1); pl_status = axis->setIntegerParam(this->motorStatusCommsError_, 1);
} }
@ -420,6 +426,15 @@ asynStatus pmacv3Controller::writeInt32(asynUser *pasynUser, epicsInt32 value) {
} }
} }
asynStatus pmacv3Controller::errMsgCouldNotParseResponse(
const char *command, const char *response, int axisNo,
const char *functionName, int lineNumber) {
char modifiedResponse[MAXBUF_] = {0};
adjustResponseForPrint(modifiedResponse, response);
return sinqController::errMsgCouldNotParseResponse(
command, modifiedResponse, axisNo, functionName, lineNumber);
}
/*************************************************************************************/ /*************************************************************************************/
/** The following functions are C-wrappers, and can be called directly from /** The following functions are C-wrappers, and can be called directly from
* iocsh */ * iocsh */
@ -533,9 +548,9 @@ types and then providing "factory" functions
(configCreateControllerCallFunc). These factory functions are used to (configCreateControllerCallFunc). These factory functions are used to
register the constructors during compilation. register the constructors during compilation.
*/ */
static const iocshArg CreateControllerArg0 = {"Controller port name", static const iocshArg CreateControllerArg0 = {"Controller name (e.g. mcu1)",
iocshArgString}; iocshArgString};
static const iocshArg CreateControllerArg1 = {"Low level port name", static const iocshArg CreateControllerArg1 = {"Asyn IP port name (e.g. pmcu1)",
iocshArgString}; iocshArgString};
static const iocshArg CreateControllerArg2 = {"Number of axes", iocshArgInt}; static const iocshArg CreateControllerArg2 = {"Number of axes", iocshArgInt};
static const iocshArg CreateControllerArg3 = {"Moving poll rate (s)", static const iocshArg CreateControllerArg3 = {"Moving poll rate (s)",
@ -558,7 +573,8 @@ static void configPmacV3CreateControllerCallFunc(const iocshArgBuf *args) {
Same procedure as for the CreateController function, but for the axis Same procedure as for the CreateController function, but for the axis
itself. 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 CreateAxisArg1 = {"Axis number", iocshArgInt};
static const iocshArg *const CreateAxisArgs[] = {&CreateAxisArg0, static const iocshArg *const CreateAxisArgs[] = {&CreateAxisArg0,
&CreateAxisArg1}; &CreateAxisArg1};

View File

@ -51,10 +51,9 @@ class pmacv3Controller : public sinqController {
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 * The function is overloaded to allow rereading the encoder and config.
* rereading the encoder.
* *
* @param pasynUser Specify the axis via the asynUser * @param pasynUser Specify the axis via the asynUser
* @param value New value * @param value New value
@ -93,6 +92,29 @@ class pmacv3Controller : public sinqController {
*/ */
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: private:
// Set the maximum buffer size. This is an empirical value which must be // Set the maximum buffer size. This is an empirical value which must be
// large enough to avoid overflows for all commands to the device / // large enough to avoid overflows for all commands to the device /
@ -109,13 +131,6 @@ class pmacv3Controller : public sinqController {
int readConfig_; int readConfig_;
int encoderType_; int encoderType_;
/*
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;
}; };