smathis bfda809257 Added high default value for RDBD
See docstring: This is to prevent overshoot stop commands.
2025-04-23 13:46:02 +02:00
2025-04-23 13:46:02 +02:00
2024-11-12 15:27:24 +01:00
2025-04-23 13:46:02 +02:00

sinqmotor

Overview

This library offers base classes for EPICS motor drivers (sinqAxis and sinqController) of PSI SINQ. These classes are extensions of the classes asynMotorAxis and asynMotorController from the asynMotor framework (https://github.com/epics-modules/motor/tree/master/motorApp/MotorSrc) and bundle some common functionality.

User guide

Architecture of EPICS motor drivers at SINQ

The asyn-framework offers two base classes asynMotorAxis and asynMotorController. At SINQ, we extend those classes by two children sinqAxis and sinqController which are not complete drivers on their own, but serve as a framework extension for writing drivers. The concrete drivers are then created as separated libraries, an example is the TurboPMAC-driver: https://git.psi.ch/sinq-epics-modules/turboPmac.

The full inheritance chain for two different motor drivers "a" and "b" looks like this: asynController -> sinqController -> aController asynAxis -> sinqAxis -> aAxis

asynController -> sinqController -> bController asynAxis -> sinqAxis -> bAxis

Those inheritance chains are created at runtime by loading shared libraries. Therefore, it is important to load compatible versions. At SINQ, the versioning numbers follow the SemVer standard (https://semver.org/lang/de/). For example, if driver "a" depends on version 2.1.0 of "sinqMotor", then it is safe to use "sinqMotor" 2.5.3 since 2.5.3 is backwards compatible to 2.1.0. However, it is not allowed to use e.g. version 1.9.0 or 2.0.0 or 3.0.1 instead. For more details on SemVer, please refer to the official documentation.

To find out which version of sinqMotor is needed by driver "a", refer to its Makefile (line sinqMotor_VERSION=x.x.x, where x.x.x is the minimum required version).

IOC startup script

An EPICS IOC for motor control at SINQ is started by executing a script with the IOC shell. In its simplest form, an IOC for two controllers is a file looking like this:

#!/usr/local/bin/iocsh  

# Load libraries needed for the IOC
require sinqMotor, 1.0.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
< actualDriver.cmd 
< actualDriver.cmd 

iocInit()

The first line is a so-called shebang which instructs Linux to execute the file with the executable located at the given path - the IOC shell in this case. The controller script "mcu1.cmd" looks like this: The script for controller 1 ("turboPmac1.cmd") for a Turbo PMAC (see https://git.psi.ch/sinq-epics-modules/turboPmac) has the following structure. The scripts for other controller types can be found in the README.md of their respective repositories.

# Define the name of the controller and the corresponding port
epicsEnvSet("DRIVER_PORT","actualDriver1")
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
drvAsynIPPortConfigure("$(IP_PORT)","172.28.101.24:1025")

# Create the controller object with the defined name and connect it to the socket via the port name.
# The other parameters are as follows:
# 8: Maximum number of axes
# 0.05: Busy poll period in seconds
# 1: Idle poll period in seconds
# 1: Socket communication timeout in seconds
actualDriverController("$(DRIVER_PORT)", "$(IP_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!
actualDriverAxis("$(DRIVER_PORT)",1);
actualDriverAxis("$(DRIVER_PORT)",2);
actualDriverAxis("$(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.
setThresholdComTimeout("$(DRIVER_PORT)", 300, 10);

# Parametrize the EPICS record database with the substitution file named after the motor controller.
epicsEnvSet("SINQDBPATH","$(sinqMotor_DB)/sinqMotor.db")
dbLoadTemplate("$(TOP)/$(DRIVER_PORT).substitutions", "INSTR=$(INSTR)$(DRIVER_PORT):,CONTROLLER=$(DRIVER_PORT)")
epicsEnvSet("SINQDBPATH","$(actualDriver_DB)/turboPmac.db")
dbLoadTemplate("$(TOP)/$(DRIVER_PORT).substitutions", "INSTR=$(INSTR)$(DRIVER_PORT):,CONTROLLER=$(DRIVER_PORT)")
dbLoadRecords("$(sinqMotor_DB)/asynRecord.db","P=$(INSTR)$(DRIVER_PORT),PORT=$(IP_PORT)")

Substitution file

The substitution file is a table containing axis-specific information which is used to create the axis-specific PVs. To work with sinqMotor, "mcu1.substitutions" needs to look like this (the order of columns does not matter):

file "$(SINQDBPATH)"
{
pattern
{ 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.

Mandatory parameters

  • AXIS: Index of the axis, corresponds to the physical connection of the axis to the MCU.
  • M: The full PV name is created by concatenating the variables INSTR, DRIVER_PORT and M. For example, the PV of the first axis would be "SQ:SINQTEST:mcu1:lin1".
  • EGU: Engineering units. For a linear motor, this is mm, for a rotaty motor, this is degree.
  • DIR: If set to "Neg", the axis direction is inverted.
  • MRES: This is a scaling factor determining the resolution of the position readback value. For example, 0.001 means a precision of 1 um. A detailed description can be found in section Motor record resolution MRES.

Optional parameters

The default values for those parameters are given for the individual records in db/sinqMotor.db

  • DESC: Description of the motor. This field is just for documentation and is not needed for operating a motor. Defaults to the motor name.
  • MSGTEXTSIZE: Buffer size for the motor message record in characters. Defaults to 200 characters
  • ENABLEMOVWATCHDOG: Sets setWatchdogEnabled during IOC startup to the given value. Defaults to 0.
  • LIMITSOFFSET: If the motor limits are read out from the controller, they can be further reduced by this offset in order to avoid errors due to slight overshoot 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). 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

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 of 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 (because it was originally meant for converting from engineering units to encoder steps, which are by definition integer values). 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 .
  • 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.

Additional records

sinqMotor provides a variety of additional records. See db/sinqMotor.db for the complete list and the documentation.

Developer guide

Base classes

sinqMotor offers a variety of additional methods for children classes to standardize certain patterns (e.g. writing messages to the IOC shell and the motor message PV). For a detailed description, please see the respective function documentation in the .h-files. All of these functions can be overwritten manually if e.g. a completely different implementation of poll is required. Some functions are marked as virtual, because they are called from other functions of sinqMotor and therefore need runtime polymorphism. Functions without that marker are not called anywhere in sinqMotor.

sinqController.h

  • couldNotParseResponse: Write a standardized message if parsing a device response failed.
  • paramLibAccessFailed: Write a standardized message if accessing the parameter library failed.
  • stringifyAsynStatus: Convert the enum asynStatus into a human-readable string.
  • checkComTimeoutWatchdog: Calculates the timeout frequency (number of timeouts in a given time) and informs the user if a specified limit has been exceeded.
  • setThresholdComTimeout: Set the maximum number of timeouts and the time window size for the timeout frequency limit.
  • checkMaxSubsequentTimeouts: Check if the number of subsequent timeouts exceeds a specified limit.
  • setMaxSubsequentTimeouts: Set the limit for the number of subsequent timeouts before the user is informed.

sinqAxis.h

  • enable: This function is called if the $(INSTR)$(M):Enable PV from db/sinqMotor.db is set. This is an empty function which should be overwritten by concrete driver implementations.

  • reset: This function is called when the $(INSTR)$(M):Reset PV from db/sinqMotor.db is set. It calls doReset and performs some fast polls after doReset returns.

  • doReset: This is an empty function which should be overwritten by concrete driver implementations.

  • move: This function sets the absolute target position in the parameter library and then calls doMove.

  • doMove: This is an empty function which should be overwritten by concrete driver implementations.

  • home: This function sets the internal status flags for the homing process and then calls doHome.

  • doHome: This is an empty function which should be overwritten by concrete driver implementations.

  • poll: This is a wrapper around doPoll which performs some bookkeeping tasks before and after calling doPoll:

    Before calling doPoll:

    • Reset the status problem flag, the communication error flag and the error message.

    After calling doPoll:

    • Call checkMovTimeoutWatchdog. If the movement timed out, create an error message for the user
    • Update the readback-value for the axis enablement.
    • Reset motorStatusProblem_, motorStatusCommsError_ and motorMessageText_ if doPoll returned asynSuccess
    • Run callParamCallbacks
    • Return the status of doPoll
  • motorPosition: Returns the parameter library value of the motor position, accounted for the motor record resolution (see section "Motor record resolution MRES")

  • setMotorPosition: Writes the given value into the parameter library, accounted for the motor record resolution (see section "Motor record resolution MRES")

  • setVeloFields: Populates the motor record fields VELO (actual velocity), VBAS (minimum allowed velocity) and VMAX (maximum allowed velocity) from the driver.

  • setAcclField: Populates the motor record field ACCL from the driver.

  • startMovTimeoutWatchdog: Starts a watchdog for the movement time. This watchdog compares the actual time spent in a movement operation with an expected time, which is calculated based on the distance of the current and the target position.

  • checkMovTimeoutWatchdog: Check if the watchdog timed out.

  • setWatchdogEnabled: Enables / disables the watchdog. This function is also available in the IOC shell.

  • setOffsetMovTimeout: Set a linear offset for the expected movement time. This function is also available in the IOC shell.

  • setScaleMovTimeout: Set a scaling factor for the expected movement time. This function is also available in the IOC shell.

msgPrintControl.h

In addition to the two extension classes this library also includes a mechanism which prevents excessive repetitions of the same error message to the IOC shell via the classes msgPrintControl and msgPrintControlKey. A detailed description of the mechanism can be found in the docstring of msgPrintControl. The implementation of the poll function of sinqAxis also contains an example how to use it. Using this feature in derived drivers is entirely optional.

Versioning

The versioning is done via git tags. Git tags are recognized by the PSI build system: If you tag a version as 1.0, it will be built into the directory /ioc/modules/sinqMotor/1.0. The tag is directly coupled to a commit so that it is always clear which source code was used to build which binary.

All existing tags can be listed with git tag in the sinqMotor directory. Detailed information (author, data, commit number, commit message) regarding a specific tag can be shown with git show x.x.x, where x.x.x is the name of your version. To create a new tag, use git tag x.x.x. If the tag x.x.x is already used by another commit, git will show a corresponding error.

How to build it

This library is based on the PSI version of the EPICS motor record, which can be found here: https://git.psi.ch/epics_driver_modules/motorBase. We use a branch with a bugfix which is currently not merged into master due to resistance of the PSI userbase: https://git.psi.ch/epics_driver_modules/motorBase/-/tree/pick_fix-lockup-VAL-HOMF-VAL. This library can be build with the following steps, assuming GCC and make are available:

  • git clone https://git.psi.ch/epics_driver_modules/motorBase/-/tree/pick_fix-lockup-VAL-HOMF-VAL
  • cd motorBase
  • git tag 7.2.2. The latest version on master is currently 7.2.1, hence we increment the bugfix version counter by one
  • make install

To build sinqMotor itself, the makefile in the top directory includes all necessary steps for compiling a shared library together with the header files into /ioc/modules (using the PSI EPICS build system). Therefore it is sufficient to clone this repository to a suitable location (git clone https://git.psi.ch/sinq-epics-modules/sinqmotor/-/tree/main). Afterwards, switch to the directory (cd sinqmotor) and run make install.

To use the library when writing a concrete motor driver, include it in the makefile of your application / library the same way as other libraries such as e.g. asynMotor by adding REQUIRED+=sinqMotor to your Makefile. The version can be specified with sinqMotor_VERSION=x.x.x.

Description
Common functionality for SINQ motor controllers
Readme 334 KiB
Languages
C++ 99.3%
Makefile 0.7%