Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 62ccf046fd | |||
| 9d61852713 | |||
| e64cedb243 | |||
| 0ff5632112 | |||
| f67941d67b | |||
| 7a96ed2b71 | |||
| cf43a1c57a | |||
| 85317e24cd |
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
O.*
|
||||
.cvsignore
|
||||
.vscode
|
||||
src/.vscode
|
||||
utils/analyzeTcpDump/__pycache__
|
||||
utils/analyzeTcpDump/demo.pcap.json
|
||||
utils/analyzeTcpDump/demovenv
|
||||
utils/analyzeTcpDump/venv
|
||||
3
Makefile
3
Makefile
@@ -34,4 +34,5 @@ TEMPLATES += db/turboPmac.db
|
||||
DBDS += sinqMotor/src/sinqMotor.dbd
|
||||
DBDS += src/turboPmac.dbd
|
||||
|
||||
USR_CFLAGS += -Wall -Wextra -Wunused-result -Werror -fvisibility=hidden # -Wpedantic // Does not work because EPICS macros trigger warnings
|
||||
USR_CFLAGS += -Wall -Wextra -Wunused-result -Werror -fvisibility=hidden # -Wpedantic // Does not work because EPICS macros trigger warnings
|
||||
USR_CXXFLAGS += -Wall -Wextra -Wunused-result -Werror -fvisibility=hidden
|
||||
44
README.md
44
README.md
@@ -4,15 +4,26 @@
|
||||
|
||||
## Overview
|
||||
|
||||
This is a driver for the Turbo PMAC motion controller with the SINQ communication protocol. It is based on the sinqMotor shared library (https://gitea.psi.ch/lin-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 Turbo PMAC motion controller with the SINQ
|
||||
communication protocol. It is based on the sinqMotor shared library
|
||||
(https://gitea.psi.ch/lin-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.
|
||||
|
||||
## User guide
|
||||
|
||||
This driver is a standard sinqMotor-derived which however uses a special low level IP Port driver (`pmacAsynIPPortConfigure`) instead of the standard `drvAsynIPPortConfigure`. For the general configuration, please see https://gitea.psi.ch/lin-epics-modules/sinqMotor/src/branch/main/README.md.
|
||||
This driver is a standard sinqMotor-derived which however uses a special low
|
||||
level IP Port driver (`pmacAsynIPPortConfigure`) instead of the standard
|
||||
`drvAsynIPPortConfigure`. For the general configuration, please see
|
||||
https://gitea.psi.ch/lin-epics-modules/sinqMotor/src/branch/main/README.md.
|
||||
|
||||
The folder "utils" contains utility scripts for working with pmac motor controllers. To read their manual, run the scripts without any arguments.
|
||||
- writeRead.py: Allows sending commands to and receiving commands from a pmac controller over an ethernet connection.
|
||||
- analyzeTcpDump.py: Parse the TCP communication between an IOC and a MCU and format it into a dictionary. "demo.py" shows how this data can be easily visualized for analysis.
|
||||
The folder "utils" contains utility scripts for working with pmac motor
|
||||
controllers. To read their manual, run the scripts without any arguments.
|
||||
- writeRead.py: Allows sending commands to and receiving commands from a pmac
|
||||
controller over an ethernet connection.
|
||||
- analyzeTcpDump.py: Parse the TCP communication between an IOC and a MCU and
|
||||
format it into a dictionary. "demo.py" shows how this data can be easily
|
||||
visualized for analysis.
|
||||
|
||||
### IOC startup script
|
||||
|
||||
@@ -27,8 +38,10 @@ The full turboPmacX.cmd file looks like this:
|
||||
epicsEnvSet("DRIVER_PORT","turboPmacX")
|
||||
epicsEnvSet("IP_PORT","p$(DRIVER_PORT)")
|
||||
|
||||
# Create the TCP/IP socket used to talk with the controller. The socket can be adressed from within the IOC shell via the port name.
|
||||
# We do not use the standard asyn port driver here, but a PMAC-specific one which enables the usage of StreamDevices for
|
||||
# Create the TCP/IP socket used to talk with the controller. The socket can be
|
||||
# adressed from within the IOC shell via the port name.
|
||||
# We do not use the standard asyn port driver here, but a PMAC-specific one
|
||||
# which enables the usage of StreamDevices for
|
||||
# communicating with the controller directly.
|
||||
pmacAsynIPPortConfigure("$(IP_PORT)","172.28.101.24:1025")
|
||||
|
||||
@@ -48,7 +61,8 @@ turboPmacAxis("$(DRIVER_PORT)",5);
|
||||
# Set the number of subsequent timeouts
|
||||
setMaxSubsequentTimeouts("$(DRIVER_PORT)", 20);
|
||||
|
||||
# Configure the timeout frequency watchdog: A maximum of 10 timeouts are allowed in 300 seconds before an alarm message is sent.
|
||||
# Configure the timeout frequency watchdog: A maximum of 10 timeouts are allowed
|
||||
# in 300 seconds before an alarm message is sent.
|
||||
setThresholdComTimeout("$(DRIVER_PORT)", 300, 10);
|
||||
|
||||
# Parametrize the EPICS record database with the substitution file named after the MCU.
|
||||
@@ -61,14 +75,22 @@ dbLoadRecords("$(turboPmac_DB)/asynRecord.db","P=$(INSTR)$(DRIVER_PORT),PORT=$(I
|
||||
|
||||
### Additional records
|
||||
|
||||
`turboPmac` provides a variety of additional records. See `db/turboPmac.db` for the complete list and the documentation.
|
||||
`turboPmac` provides a variety of additional records. See `db/turboPmac.db` for
|
||||
the complete list and the documentation.
|
||||
|
||||
## Developer guide
|
||||
|
||||
### Versioning
|
||||
|
||||
Please see the documentation for the module sinqMotor: https://gitea.psi.ch/lin-epics-modules/sinqMotor/src/branch/main/README.md.
|
||||
Please see the documentation for the module sinqMotor:
|
||||
https://gitea.psi.ch/lin-epics-modules/sinqMotor/src/branch/main/README.md.
|
||||
|
||||
### How to build it
|
||||
|
||||
This driver can be compiled and installed by running `make install` from the same directory where the Makefile is located. However, since it uses the git submodule sinqMotor, make sure that the correct version of the submodule repository is checked out AND the change is commited (`git status` shows no non-committed changes). Please see the section "Usage as static dependency" in https://gitea.psi.ch/lin-epics-modules/sinqMotor/src/branch/main/README.md for more details.
|
||||
This driver can be compiled and installed by running `make install` from the
|
||||
same directory where the Makefile is located. However, since it uses the git
|
||||
submodule sinqMotor, make sure that the correct version of the submodule
|
||||
repository is checked out AND the change is commited (`git status` shows no
|
||||
non-committed changes). Please see the section "Usage as static dependency" in
|
||||
https://gitea.psi.ch/lin-epics-modules/sinqMotor/src/branch/main/README.md for
|
||||
more details.
|
||||
|
||||
BIN
TurboPMAC_manual.pdf
Normal file
BIN
TurboPMAC_manual.pdf
Normal file
Binary file not shown.
Submodule sinqMotor updated: 7a0de4e9d9...e234d05815
@@ -70,12 +70,13 @@ turboPmacAxis::turboPmacAxis(turboPmacController *pC, int axisNo,
|
||||
axes.push_back(this);
|
||||
}
|
||||
|
||||
pTurboPmacA_ = std::make_unique<turboPmacAxisImpl>(
|
||||
(turboPmacAxisImpl){.waitForHandshake = false,
|
||||
.enableDisable = false,
|
||||
.timeAtHandshake = 0,
|
||||
.axisStatus = 0,
|
||||
.needInit = false});
|
||||
pTurboPmacA_ = std::make_unique<turboPmacAxisImpl>((turboPmacAxisImpl){
|
||||
.waitForHandshake = false,
|
||||
.enableDisable = false,
|
||||
.timeAtHandshake = 0,
|
||||
.axisStatus = 0,
|
||||
.needInit = false,
|
||||
});
|
||||
|
||||
// Provide initial values for some parameter library entries
|
||||
status = pC_->setIntegerParam(axisNo_, pC_->rereadEncoderPosition(), 0);
|
||||
@@ -132,9 +133,16 @@ asynStatus turboPmacAxis::init() {
|
||||
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;
|
||||
|
||||
// Conversion factor between engineering units (EU) and encoder counts
|
||||
double counts_to_eu = 0.0;
|
||||
|
||||
// Deadband in counts. Can be a fraction of a count, hence double.
|
||||
double deadband_counts = 0.0;
|
||||
|
||||
// Offset time for the movement watchdog caused by the air cushions in
|
||||
// milliseconds.
|
||||
int acoDelay = 0.0;
|
||||
|
||||
// The parameter library takes some time to be initialized. Therefore we
|
||||
// wait until the status is not asynParamUndefined anymore.
|
||||
@@ -163,12 +171,14 @@ asynStatus turboPmacAxis::init() {
|
||||
|
||||
/*
|
||||
Read out the axis status, the current position, current and maximum speed,
|
||||
acceleration and the air cushion delay.
|
||||
acceleration, the air cushion delay, the scaling factor between counts and
|
||||
engineering units ([Qxx00] = engineering units / counts) and the deadband
|
||||
Ixx65.
|
||||
*/
|
||||
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);
|
||||
"Q%2.2d10 Q%2.2d03 Q%2.2d04 Q%2.2d06 P%2.2d22 Q%2.2d00 I%2.2d65",
|
||||
axisNo_, axisNo_, axisNo_, axisNo_, axisNo_, axisNo_, axisNo_);
|
||||
status = pC_->writeRead(axisNo_, command, response, 7);
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(
|
||||
pC_->pasynUser(), ASYN_TRACE_ERROR,
|
||||
@@ -180,8 +190,9 @@ asynStatus turboPmacAxis::init() {
|
||||
pC_->portName, axisNo(), __PRETTY_FUNCTION__, __LINE__);
|
||||
pTurboPmacA_->needInit = true;
|
||||
}
|
||||
nvals = sscanf(response, "%d %lf %lf %lf %lf %d", &axStatus, &motorPos,
|
||||
&motorVmax, &motorVelocity, &motorAccel, &acoDelay);
|
||||
nvals = sscanf(response, "%lf %lf %lf %lf %d %lf %lf", &motorPos,
|
||||
&motorVmax, &motorVelocity, &motorAccel, &acoDelay,
|
||||
&counts_to_eu, &deadband_counts);
|
||||
|
||||
// The acoDelay is given in milliseconds -> Convert to seconds, rounded up
|
||||
setOffsetMovTimeout(std::ceil(acoDelay / 1000.0));
|
||||
@@ -191,7 +202,7 @@ asynStatus turboPmacAxis::init() {
|
||||
// here to mm/s^2.
|
||||
motorAccel = motorAccel * 1000;
|
||||
|
||||
if (nvals != 6) {
|
||||
if (nvals != 8) {
|
||||
return pC_->couldNotParseResponse(command, response, axisNo_,
|
||||
__PRETTY_FUNCTION__, __LINE__);
|
||||
}
|
||||
@@ -215,6 +226,26 @@ asynStatus turboPmacAxis::init() {
|
||||
return status;
|
||||
}
|
||||
|
||||
/*
|
||||
Check if the new target position is within the range current position +/-
|
||||
deadband. If that is the case, no movement command should be sent. This
|
||||
functionality is implemented within the motor record itself, we just need to
|
||||
populate the field SPDP (see record PushSPDB2Field in db/turboPmac.db)
|
||||
|
||||
First, the deadband is read out (Ixx65, see Turbo PMAC User Manual, p.
|
||||
160). This value then needs to be converted into counts (by dividing it by
|
||||
16). After words, the deadband in counts then need to be converted into
|
||||
engineering units. For this, the scaling factor Qxx10 needs to be applied
|
||||
(see SINQ_2G_MCU_SW_manual_rev7, p. 17):
|
||||
[Qxx10] = engineering units (eu) / counts
|
||||
deadband_eu = Qxx10 * Ixx65 / 16
|
||||
|
||||
The values of Qxx10 and Ixx65 are read out during initialization and are
|
||||
assumed to not change during operation.
|
||||
*/
|
||||
setAxisParamChecked(this, motorPositionDeadband,
|
||||
counts_to_eu * deadband_counts / 16.0);
|
||||
|
||||
// Update the parameter library immediately
|
||||
status = callParamCallbacks();
|
||||
if (status != asynSuccess) {
|
||||
@@ -521,11 +552,6 @@ asynStatus turboPmacAxis::doPoll(bool *moving) {
|
||||
|
||||
errorStatus = handleError(error, userMessage, sizeof(userMessage));
|
||||
|
||||
// Update the parameter library
|
||||
if (error != 0) {
|
||||
setAxisParamChecked(this, motorStatusProblem, true);
|
||||
}
|
||||
|
||||
if (*moving == false) {
|
||||
setAxisParamChecked(this, motorMoveToHome, false);
|
||||
}
|
||||
@@ -752,7 +778,7 @@ asynStatus turboPmacAxis::doMove(double position, int relative,
|
||||
|
||||
char command[pC_->MAXBUF_] = {0};
|
||||
char response[pC_->MAXBUF_] = {0};
|
||||
double motorCoordinatesPosition = 0.0;
|
||||
double motorTargetPosition = 0.0;
|
||||
double motorRecResolution = 0.0;
|
||||
double motorVelocity = 0.0;
|
||||
int enabled = 0;
|
||||
@@ -761,6 +787,10 @@ asynStatus turboPmacAxis::doMove(double position, int relative,
|
||||
|
||||
// =========================================================================
|
||||
|
||||
// Suppress unused variables warning
|
||||
(void)minVelocity;
|
||||
(void)acceleration;
|
||||
|
||||
getAxisParamChecked(this, motorEnableRBV, &enabled);
|
||||
getAxisParamChecked(this, motorRecResolution, &motorRecResolution);
|
||||
|
||||
@@ -773,13 +803,14 @@ asynStatus turboPmacAxis::doMove(double position, int relative,
|
||||
}
|
||||
|
||||
// Convert from EPICS to user / motor units
|
||||
motorCoordinatesPosition = position * motorRecResolution;
|
||||
motorTargetPosition = position * motorRecResolution;
|
||||
motorVelocity = maxVelocity * motorRecResolution;
|
||||
|
||||
asynPrint(pC_->pasynUser(), ASYN_TRACE_FLOW,
|
||||
"Controller \"%s\", axis %d => %s, line %d\nStart of axis to "
|
||||
"position %lf.\n",
|
||||
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, position);
|
||||
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
|
||||
motorTargetPosition);
|
||||
|
||||
// Check if the speed is allowed to be changed
|
||||
getAxisParamChecked(this, motorCanSetSpeed, &motorCanSetSpeed);
|
||||
@@ -804,11 +835,11 @@ asynStatus turboPmacAxis::doMove(double position, int relative,
|
||||
if (relative) {
|
||||
snprintf(&command[writeOffset], sizeof(command) - writeOffset,
|
||||
"P%2.2d23=0 Q%2.2d02=%lf M%2.2d=2", axisNo_, axisNo_,
|
||||
motorCoordinatesPosition, axisNo_);
|
||||
motorTargetPosition, axisNo_);
|
||||
} else {
|
||||
snprintf(&command[writeOffset], sizeof(command) - writeOffset,
|
||||
"P%2.2d23=0 Q%2.2d01=%lf M%2.2d=1", axisNo_, axisNo_,
|
||||
motorCoordinatesPosition, axisNo_);
|
||||
motorTargetPosition, axisNo_);
|
||||
}
|
||||
|
||||
// We don't expect an answer
|
||||
@@ -820,7 +851,7 @@ asynStatus turboPmacAxis::doMove(double position, int relative,
|
||||
"Controller \"%s\", axis %d => %s, line %d\nStarting movement to "
|
||||
"target position %lf failed.\n",
|
||||
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
|
||||
motorCoordinatesPosition);
|
||||
motorTargetPosition);
|
||||
setAxisParamChecked(this, motorStatusProblem, true);
|
||||
return status;
|
||||
}
|
||||
@@ -848,6 +879,9 @@ asynStatus turboPmacAxis::stop(double acceleration) {
|
||||
|
||||
// =========================================================================
|
||||
|
||||
// Suppress unused variables warning
|
||||
(void)acceleration;
|
||||
|
||||
snprintf(command, sizeof(command), "M%2.2d=8", axisNo_);
|
||||
status = pC_->writeRead(axisNo_, command, response, 0);
|
||||
|
||||
@@ -912,7 +946,7 @@ asynStatus turboPmacAxis::doReset() {
|
||||
/*
|
||||
Home the axis. On absolute encoder systems, this is a no-op
|
||||
*/
|
||||
asynStatus turboPmacAxis::doHome(double min_velocity, double max_velocity,
|
||||
asynStatus turboPmacAxis::doHome(double minVelocity, double maxVelocity,
|
||||
double acceleration, int forwards) {
|
||||
|
||||
// Status of read-write-operations of ASCII commands to the controller
|
||||
@@ -923,6 +957,12 @@ asynStatus turboPmacAxis::doHome(double min_velocity, double max_velocity,
|
||||
|
||||
// =========================================================================
|
||||
|
||||
// Suppress unused variables warning
|
||||
(void)minVelocity;
|
||||
(void)maxVelocity;
|
||||
(void)acceleration;
|
||||
(void)forwards;
|
||||
|
||||
getAxisParamChecked(this, encoderType, &response);
|
||||
|
||||
// Only send the home command if the axis has an incremental encoder
|
||||
|
||||
@@ -13,6 +13,10 @@ class HIDDEN turboPmacAxis : public sinqAxis {
|
||||
*
|
||||
* @param pController Pointer to the associated controller
|
||||
* @param axisNo Index of the axis
|
||||
* @param initialize By setting this parameter to false, the
|
||||
* initialization functions of the axes are not executed. This is e.g.
|
||||
* necessary when this constructor is called from a children class
|
||||
* constructor which performs its own initialization.
|
||||
*/
|
||||
turboPmacAxis(turboPmacController *pController, int axisNo,
|
||||
bool initialize = true);
|
||||
@@ -24,6 +28,18 @@ class HIDDEN turboPmacAxis : public sinqAxis {
|
||||
*/
|
||||
virtual ~turboPmacAxis();
|
||||
|
||||
/**
|
||||
* @brief Readout of some values from the controller at IOC startup
|
||||
*
|
||||
* The following steps are performed:
|
||||
* - Read out the motor status, motor position, velocity and acceleration
|
||||
* from the MCU and store this information in the parameter library.
|
||||
* - Set the enable PV according to the initial status of the axis.
|
||||
*
|
||||
* @return asynStatus
|
||||
*/
|
||||
virtual asynStatus init();
|
||||
|
||||
/**
|
||||
* @brief Implementation of the `stop` function from asynMotorAxis
|
||||
*
|
||||
@@ -70,18 +86,6 @@ class HIDDEN turboPmacAxis : public sinqAxis {
|
||||
double min_velocity, double max_velocity,
|
||||
double acceleration);
|
||||
|
||||
/**
|
||||
* @brief Readout of some values from the controller at IOC startup
|
||||
*
|
||||
* The following steps are performed:
|
||||
* - Read out the motor status, motor position, velocity and acceleration
|
||||
* from the MCU and store this information in the parameter library.
|
||||
* - Set the enable PV according to the initial status of the axis.
|
||||
*
|
||||
* @return asynStatus
|
||||
*/
|
||||
virtual asynStatus init();
|
||||
|
||||
/**
|
||||
* @brief Implementation of the `doReset` function from sinqAxis.
|
||||
*
|
||||
|
||||
@@ -47,7 +47,7 @@ struct turboPmacControllerImpl {
|
||||
int flushHardware;
|
||||
int limFromHardware;
|
||||
};
|
||||
#define NUM_turboPmac_DRIVER_PARAMS 3
|
||||
#define NUM_turboPmac_DRIVER_PARAMS 5
|
||||
|
||||
turboPmacController::turboPmacController(const char *portName,
|
||||
const char *ipPortConfigName,
|
||||
@@ -61,17 +61,17 @@ turboPmacController::turboPmacController(const char *portName,
|
||||
- REREAD_ENCODER_POSITION
|
||||
- READ_CONFIG
|
||||
*/
|
||||
numExtraParams + NUM_turboPmac_DRIVER_PARAMS)
|
||||
|
||||
{
|
||||
|
||||
// The paramLib indices are populated with the calls to createParam
|
||||
pTurboPmacC_ =
|
||||
std::make_unique<turboPmacControllerImpl>((turboPmacControllerImpl){
|
||||
.comTimeout = comTimeout,
|
||||
.lastResponse = {0},
|
||||
});
|
||||
|
||||
numExtraParams + NUM_turboPmac_DRIVER_PARAMS),
|
||||
pTurboPmacC_(
|
||||
std::make_unique<turboPmacControllerImpl>((turboPmacControllerImpl){
|
||||
.comTimeout = comTimeout,
|
||||
.lastResponse = {0},
|
||||
.pasynInt32SyncIOipPort = nullptr, // Populated in constructor
|
||||
.rereadEncoderPosition = 0, // Populated in constructor
|
||||
.readConfig = 0, // Populated in constructor
|
||||
.flushHardware = 0, // Populated in constructor
|
||||
.limFromHardware = 0, // Populated in constructor
|
||||
})) {
|
||||
// Initialization of local variables
|
||||
asynStatus status = asynSuccess;
|
||||
|
||||
@@ -573,7 +573,8 @@ static const iocshArg *const CreateControllerArgs[] = {
|
||||
&CreateControllerArg0, &CreateControllerArg1, &CreateControllerArg2,
|
||||
&CreateControllerArg3, &CreateControllerArg4, &CreateControllerArg5};
|
||||
static const iocshFuncDef configTurboPmacCreateController = {
|
||||
"turboPmacController", 6, CreateControllerArgs};
|
||||
"turboPmacController", 6, CreateControllerArgs,
|
||||
"Create a new instance of a TurboPMAC controller."};
|
||||
static void configTurboPmacCreateControllerCallFunc(const iocshArgBuf *args) {
|
||||
turboPmacCreateController(args[0].sval, args[1].sval, args[2].ival,
|
||||
args[3].dval, args[4].dval, args[5].dval);
|
||||
|
||||
Reference in New Issue
Block a user