Added adaptive polling
See documentation in README.md for parameter ADAPTPOLL
This commit is contained in:
29
README.md
29
README.md
@ -29,15 +29,15 @@ An EPICS IOC for motor control at SINQ is started by executing a script with the
|
||||
|
||||
# Load libraries needed for the IOC
|
||||
require sinqMotor, 1.0.0
|
||||
require turboPmac, 1.2.0
|
||||
require actualDriver, 1.2.0
|
||||
|
||||
# Define environment variables used later to parametrize the individual controllers
|
||||
epicsEnvSet("TOP","/ioc/sinq-ioc/sinqtest-ioc/")
|
||||
epicsEnvSet("INSTR","SQ:SINQTEST:")
|
||||
|
||||
# Include other scripts for the controllers 1 and 2
|
||||
< turboPmac1.cmd
|
||||
< turboPmac2.cmd
|
||||
< actualDriver.cmd
|
||||
< actualDriver.cmd
|
||||
|
||||
iocInit()
|
||||
```
|
||||
@ -46,7 +46,7 @@ The script for controller 1 ("turboPmac1.cmd") for a Turbo PMAC (see https://git
|
||||
|
||||
```
|
||||
# Define the name of the controller and the corresponding port
|
||||
epicsEnvSet("NAME","turboPmac1")
|
||||
epicsEnvSet("NAME","actualDriver1")
|
||||
epicsEnvSet("ASYN_PORT","p$(NAME)")
|
||||
|
||||
# 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
|
||||
@ -58,12 +58,12 @@ drvAsynIPPortConfigure("$(ASYN_PORT)","172.28.101.24:1025")
|
||||
# 0.05: Busy poll period in seconds
|
||||
# 1: Idle poll period in seconds
|
||||
# 1: Socket communication timeout in seconds
|
||||
turboPmacController("$(NAME)", "$(ASYN_PORT)", 8, 0.05, 1, 1);
|
||||
actualDriverController("$(NAME)", "$(ASYN_PORT)", 8, 0.05, 1, 1);
|
||||
|
||||
# Define some axes for the specified motor controller at the given slot (1, 2 and 5). No slot may be used twice!
|
||||
turboPmacAxis("$(NAME)",1);
|
||||
turboPmacAxis("$(NAME)",2);
|
||||
turboPmacAxis("$(NAME)",5);
|
||||
actualDriverAxis("$(NAME)",1);
|
||||
actualDriverAxis("$(NAME)",2);
|
||||
actualDriverAxis("$(NAME)",5);
|
||||
|
||||
# Set the number of subsequent timeouts
|
||||
setMaxSubsequentTimeouts("$(NAME)", 20);
|
||||
@ -74,7 +74,7 @@ setThresholdComTimeout("$(NAME)", 300, 10);
|
||||
# Parametrize the EPICS record database with the substitution file named after the motor controller.
|
||||
epicsEnvSet("SINQDBPATH","$(sinqMotor_DB)/sinqMotor.db")
|
||||
dbLoadTemplate("$(TOP)/$(NAME).substitutions", "INSTR=$(INSTR)$(NAME):,CONTROLLER=$(NAME)")
|
||||
epicsEnvSet("SINQDBPATH","$(turboPmac_DB)/turboPmac.db")
|
||||
epicsEnvSet("SINQDBPATH","$(actualDriver_DB)/turboPmac.db")
|
||||
dbLoadTemplate("$(TOP)/$(NAME).substitutions", "INSTR=$(INSTR)$(NAME):,CONTROLLER=$(NAME)")
|
||||
dbLoadRecords("$(sinqMotor_DB)/asynRecord.db","P=$(INSTR)$(NAME),PORT=$(ASYN_PORT)")
|
||||
```
|
||||
@ -87,11 +87,11 @@ To work with sinqMotor, "mcu1.substitutions" needs to look like this (the order
|
||||
file "$(SINQDBPATH)"
|
||||
{
|
||||
pattern
|
||||
{ AXIS, M, DESC, EGU, DIR, MRES, MSGTEXTSIZE, ENABLEMOVWATCHDOG, LIMITSOFFSET, CANSETSPEED }
|
||||
{ 1, "lin1", "Linear motor doing whatever", mm, Pos, 0.001, 200, 1, 1.0, 1 }
|
||||
{ 2, "rot1", "First rotary motor", degree, Neg, 0.001, 200, 0, 1.0, 0 }
|
||||
{ 3, "rot2", "Second rotary motor", degree, Pos, 0.001, 200, 0, 0.0, 1 }
|
||||
{ 5, "rot3", "Surprise: Third rotary motor", degree, Pos, 0.001, 200, 1, 2.0, 0 }
|
||||
{ AXIS, M, DESC, EGU, DIR, MRES, MSGTEXTSIZE, ENABLEMOVWATCHDOG, LIMITSOFFSET, CANSETSPEED, ADAPTPOLL }
|
||||
{ 1, "lin1", "Linear motor doing whatever", mm, Pos, 0.001, 200, 1, 1.0, 1, 1 }
|
||||
{ 2, "rot1", "First rotary motor", degree, Neg, 0.001, 200, 0, 1.0, 0, 1 }
|
||||
{ 3, "rot2", "Second rotary motor", degree, Pos, 0.001, 200, 0, 0.0, 1, 0 }
|
||||
{ 5, "rot3", "Surprise: Third rotary motor", degree, Pos, 0.001, 200, 1, 2.0, 0, 0 }
|
||||
}
|
||||
```
|
||||
The variable `SINQDBPATH` has been set in "mcu1.cmd" before calling `dbLoadTemplate`.
|
||||
@ -114,6 +114,7 @@ be further reduced by this offset in order to avoid errors due to slight oversho
|
||||
on the motor controller. For example, if this value is 1.0 and the read-out limits
|
||||
are [-10.0 10.0], the EPICS limits are set to [-9.0 9.0]. This parameter uses engineering units (EGU). Defaults to 0.0.
|
||||
- `CANSETSPEED`: If set to 1, the motor speed can be modified by the user. Defaults to 0.
|
||||
- `ADAPTPOLL`: If set to any value other than 0, adaptive polling is enabled for this particular axis. Adaptive polling is designed to reduce the communication load in case some axis is moving. By default, if at least one axis is moving, all axes are polled using the busy / moving poll period (see [IOC startup script](#ioc-startup-script)). Adaptive polling modifies this behaviour so that the affected axis is only polled with the busy / moving poll period if it itself is moving. Defaults to 1.
|
||||
|
||||
### Motor record resolution MRES
|
||||
|
||||
|
@ -140,6 +140,18 @@ record(longout, "$(INSTR)$(M):CanSetSpeed") {
|
||||
field(VAL, "$(CANSETSPEED=0)")
|
||||
}
|
||||
|
||||
# If this PV has a value other than 0, adaptive polling for this axis is enabled.
|
||||
# The standard motor record behaviour is to poll all axis with the busy / move poll
|
||||
# period if at least one of the axes is moving. Adaptive polling changes this so
|
||||
# that only axes which were moving in the last poll are polled with the busy / move poll
|
||||
# period and all other axes are polled with the idle poll period.
|
||||
record(longout, "$(INSTR)$(M):AdaptivePolling") {
|
||||
field(DTYP, "asynInt32")
|
||||
field(OUT, "@asyn($(CONTROLLER),$(AXIS),1) ADAPTIVE_POLLING")
|
||||
field(PINI, "YES")
|
||||
field(VAL, "$(ADAPTPOLL=1)")
|
||||
}
|
||||
|
||||
# The timeout mechanism for movements can be enabled / disabled by setting
|
||||
# this PV to 1 / 0.
|
||||
# This record is coupled to the parameter library via motorEnableMovWatchdog -> MOTOR_ENABLE_MOV_WATCHDOG.
|
||||
|
@ -15,6 +15,8 @@ sinqAxis::sinqAxis(class sinqController *pC, int axisNo)
|
||||
offsetMovTimeout_ = 30;
|
||||
targetPosition_ = 0.0;
|
||||
|
||||
epicsTimeGetCurrent(&lastPollTime_);
|
||||
|
||||
// This check is also done in asynMotorAxis, but there the IOC continues
|
||||
// running even though the configuration is incorrect. When failing this
|
||||
// check, the IOC is stopped, since this is definitely a configuration
|
||||
@ -115,6 +117,46 @@ asynStatus sinqAxis::poll(bool *moving) {
|
||||
asynStatus poll_status = asynSuccess;
|
||||
int homing = 0;
|
||||
int homed = 0;
|
||||
int adaptivePolling = 0;
|
||||
|
||||
/*
|
||||
If adaptive polling is enabled:
|
||||
- Check if the motor was moving during the last poll
|
||||
- If yes, perform the poll
|
||||
- If no, check if the last poll was at least an idlePollPeriod ago
|
||||
- If yes, perform the poll
|
||||
- If no, skip it
|
||||
*/
|
||||
|
||||
pl_status =
|
||||
pC_->getIntegerParam(axisNo_, pC_->adaptivePolling(), &adaptivePolling);
|
||||
if (pl_status != asynSuccess) {
|
||||
return pC_->paramLibAccessFailed(pl_status, "adaptivePolling", axisNo_,
|
||||
__PRETTY_FUNCTION__, __LINE__);
|
||||
};
|
||||
|
||||
// Using the EPICS timestamp here, see
|
||||
// https://docs.epics-controls.org/projects/base/en/latest/epicsTime_h.html#_CPPv414epicsTimeStamp
|
||||
|
||||
// Get the current time
|
||||
epicsTimeStamp ts;
|
||||
epicsTimeGetCurrent(&ts);
|
||||
|
||||
if (adaptivePolling != 0) {
|
||||
// Motor wasn't moving during the last poll
|
||||
if (getWasMovingFlag() == 0) {
|
||||
|
||||
// Add the idle poll period
|
||||
epicsTimeStamp earliestTimeNextPoll = lastPollTime_;
|
||||
epicsTimeAddSeconds(&earliestTimeNextPoll, pC_->idlePollPeriod());
|
||||
if (epicsTimeLessThan(&earliestTimeNextPoll, &ts) == 1) {
|
||||
*moving = false;
|
||||
return asynSuccess;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Update the start time of the last poll
|
||||
lastPollTime_ = ts;
|
||||
|
||||
/*
|
||||
At the beginning of the poll, it is assumed that the axis has no status
|
||||
@ -223,36 +265,42 @@ asynStatus sinqAxis::move(double position, int relative, double minVelocity,
|
||||
double maxVelocity, double acceleration) {
|
||||
|
||||
// Status of parameter library operations
|
||||
asynStatus pl_status = asynSuccess;
|
||||
asynStatus status = asynSuccess;
|
||||
double motorRecResolution = 0.0;
|
||||
|
||||
// =========================================================================
|
||||
|
||||
// When a new move is done, the motor is not homed anymore
|
||||
pl_status = setIntegerParam(pC_->motorStatusHomed(), 0);
|
||||
if (pl_status != asynSuccess) {
|
||||
return pC_->paramLibAccessFailed(pl_status, "motorStatusHomed_",
|
||||
axisNo_, __PRETTY_FUNCTION__,
|
||||
__LINE__);
|
||||
}
|
||||
pl_status = setIntegerParam(pC_->motorStatusAtHome(), 0);
|
||||
if (pl_status != asynSuccess) {
|
||||
return pC_->paramLibAccessFailed(pl_status, "motorStatusAtHome_",
|
||||
axisNo_, __PRETTY_FUNCTION__,
|
||||
__LINE__);
|
||||
}
|
||||
pl_status = pC_->getDoubleParam(axisNo_, pC_->motorRecResolution(),
|
||||
&motorRecResolution);
|
||||
if (pl_status != asynSuccess) {
|
||||
return pC_->paramLibAccessFailed(pl_status, "motorRecResolution_",
|
||||
axisNo_, __PRETTY_FUNCTION__,
|
||||
__LINE__);
|
||||
}
|
||||
|
||||
// Store the target position internally
|
||||
targetPosition_ = position * motorRecResolution;
|
||||
|
||||
return doMove(position, relative, minVelocity, maxVelocity, acceleration);
|
||||
status = doMove(position, relative, minVelocity, maxVelocity, acceleration);
|
||||
if (status != asynSuccess) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// Since the move command was successfull, we assume that the motor has
|
||||
// started its movement.
|
||||
status = setIntegerParam(pC_->motorStatusHomed(), 0);
|
||||
if (status != asynSuccess) {
|
||||
return pC_->paramLibAccessFailed(status, "motorStatusHomed_", axisNo_,
|
||||
__PRETTY_FUNCTION__, __LINE__);
|
||||
}
|
||||
status = setIntegerParam(pC_->motorStatusAtHome(), 0);
|
||||
if (status != asynSuccess) {
|
||||
return pC_->paramLibAccessFailed(status, "motorStatusAtHome_", axisNo_,
|
||||
__PRETTY_FUNCTION__, __LINE__);
|
||||
}
|
||||
status = pC_->getDoubleParam(axisNo_, pC_->motorRecResolution(),
|
||||
&motorRecResolution);
|
||||
if (status != asynSuccess) {
|
||||
return pC_->paramLibAccessFailed(status, "motorRecResolution_", axisNo_,
|
||||
__PRETTY_FUNCTION__, __LINE__);
|
||||
}
|
||||
|
||||
// Needed for adaptive polling
|
||||
setWasMovingFlag(1);
|
||||
|
||||
return pC_->callParamCallbacks();
|
||||
}
|
||||
|
||||
asynStatus sinqAxis::doMove(double position, int relative, double minVelocity,
|
||||
|
@ -7,6 +7,7 @@ Stefan Mathis, November 2024
|
||||
#ifndef sinqAxis_H
|
||||
#define sinqAxis_H
|
||||
#include "asynMotorAxis.h"
|
||||
#include <epicsTime.h>
|
||||
|
||||
class epicsShareClass sinqAxis : public asynMotorAxis {
|
||||
public:
|
||||
@ -351,11 +352,17 @@ class epicsShareClass sinqAxis : public asynMotorAxis {
|
||||
// Internal variables used in the movement timeout watchdog
|
||||
time_t expectedArrivalTime_;
|
||||
time_t offsetMovTimeout_;
|
||||
|
||||
double scaleMovTimeout_;
|
||||
bool watchdogMovActive_;
|
||||
// Store the motor target position for the movement time calculation
|
||||
double targetPosition_;
|
||||
|
||||
/*
|
||||
Store the time since the last poll
|
||||
*/
|
||||
epicsTimeStamp lastPollTime_;
|
||||
|
||||
private:
|
||||
sinqController *pC_;
|
||||
};
|
||||
|
@ -53,7 +53,7 @@ sinqController::sinqController(const char *portName,
|
||||
asynStatus status = asynSuccess;
|
||||
|
||||
// Handle to the asynUser of the IP port asyn driver
|
||||
ipPortAsynOctetSyncIO_ = nullptr;
|
||||
pasynOctetSyncIOipPort_ = nullptr;
|
||||
|
||||
// Initial values for the average timeout mechanism, can be overwritten
|
||||
// later by a FFI function
|
||||
@ -81,9 +81,9 @@ sinqController::sinqController(const char *portName,
|
||||
We try to connect to the port via the port name provided by the constructor.
|
||||
If this fails, the function is terminated via exit.
|
||||
*/
|
||||
pasynOctetSyncIO->connect(ipPortConfigName, 0, &ipPortAsynOctetSyncIO_,
|
||||
pasynOctetSyncIO->connect(ipPortConfigName, 0, &pasynOctetSyncIOipPort_,
|
||||
NULL);
|
||||
if (status != asynSuccess || ipPortAsynOctetSyncIO_ == nullptr) {
|
||||
if (status != asynSuccess || pasynOctetSyncIOipPort_ == nullptr) {
|
||||
errlogPrintf("Controller \"%s\" => %s, line %d:\nFATAL ERROR (cannot "
|
||||
"connect to MCU controller).\n"
|
||||
"Terminating IOC",
|
||||
@ -252,6 +252,16 @@ sinqController::sinqController(const char *portName,
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
status = createParam("ADAPTIVE_POLLING", asynParamInt32, &adaptivePolling_);
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
|
||||
"parameter failed with %s).\nTerminating IOC",
|
||||
portName, __PRETTY_FUNCTION__, __LINE__,
|
||||
stringifyAsynStatus(status));
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
status = createParam("ENCODER_TYPE", asynParamOctet, &encoderType_);
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
@ -356,7 +366,7 @@ asynStatus sinqController::couldNotParseResponse(const char *command,
|
||||
int line) {
|
||||
asynStatus pl_status = asynSuccess;
|
||||
|
||||
asynPrint(ipPortAsynOctetSyncIO_, ASYN_TRACE_ERROR,
|
||||
asynPrint(pasynOctetSyncIOipPort_, ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\", axis %d => %s, line %d:\nCould not interpret "
|
||||
"response \"%s\" for command \"%s\".\n",
|
||||
portName, axisNo, functionName, line, response, command);
|
||||
@ -385,7 +395,7 @@ asynStatus sinqController::paramLibAccessFailed(asynStatus status,
|
||||
int line) {
|
||||
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(ipPortAsynOctetSyncIO_, ASYN_TRACE_ERROR,
|
||||
asynPrint(pasynOctetSyncIOipPort_, ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\", axis %d => %s, line %d:\n Accessing the "
|
||||
"parameter library failed for parameter %s with error %s.\n",
|
||||
portName, axisNo, functionName, line, parameter,
|
||||
@ -654,9 +664,9 @@ asynStatus setMaxSubsequentTimeouts(const char *portName,
|
||||
if (ptr == nullptr) {
|
||||
/*
|
||||
We can't use asynPrint here since this macro would require us
|
||||
to get a ipPortAsynOctetSyncIO_ from a pointer to an asynPortDriver.
|
||||
to get a pasynOctetSyncIOipPort_ from a pointer to an asynPortDriver.
|
||||
However, the given pointer is a nullptr and therefore doesn't
|
||||
have a ipPortAsynOctetSyncIO_! printf is an EPICS alternative which
|
||||
have a pasynOctetSyncIOipPort_! printf is an EPICS alternative which
|
||||
works w/o that, but doesn't offer the comfort provided
|
||||
by the asynTrace-facility
|
||||
*/
|
||||
|
@ -291,6 +291,7 @@ class epicsShareClass sinqController : public asynMotorController {
|
||||
int motorAcclFromDriver() { return motorAcclFromDriver_; }
|
||||
int motorHighLimitFromDriver() { return motorHighLimitFromDriver_; }
|
||||
int motorLowLimitFromDriver() { return motorLowLimitFromDriver_; }
|
||||
int adaptivePolling() { return adaptivePolling_; }
|
||||
int encoderType() { return encoderType_; }
|
||||
|
||||
// Additional members
|
||||
@ -298,14 +299,14 @@ class epicsShareClass sinqController : public asynMotorController {
|
||||
double idlePollPeriod() { return idlePollPeriod_; }
|
||||
double movingPollPeriod() { return movingPollPeriod_; }
|
||||
asynUser *asynUserSelf() { return pasynUserSelf; }
|
||||
asynUser *ipPortAsynOctetSyncIO() { return ipPortAsynOctetSyncIO_; }
|
||||
asynUser *pasynOctetSyncIOipPort() { return pasynOctetSyncIOipPort_; }
|
||||
|
||||
// =========================================================================
|
||||
|
||||
protected:
|
||||
// Pointer to the port user which is specified by the char array
|
||||
// `ipPortConfigName` in the constructor
|
||||
asynUser *ipPortAsynOctetSyncIO_;
|
||||
asynUser *pasynOctetSyncIOipPort_;
|
||||
double movingPollPeriod_;
|
||||
double idlePollPeriod_;
|
||||
msgPrintControl msgPrintControl_;
|
||||
@ -345,6 +346,7 @@ class epicsShareClass sinqController : public asynMotorController {
|
||||
int motorAcclFromDriver_;
|
||||
int motorHighLimitFromDriver_;
|
||||
int motorLowLimitFromDriver_;
|
||||
int adaptivePolling_;
|
||||
int encoderType_;
|
||||
#define LAST_SINQMOTOR_PARAM encoderType_
|
||||
|
||||
|
Reference in New Issue
Block a user