Compare commits

...

22 Commits
0.0.2 ... 0.6.3

Author SHA1 Message Date
f14ac66971 Missing controller name in PV name 2025-01-08 16:01:04 +01:00
b6e0f03a17 Moved the initialization of some parameters into sinqMotor 2024-12-23 09:30:24 +01:00
5946563372 Merge branch 'main' of https://git.psi.ch/sinq-epics-modules/sinqmotor 2024-12-11 09:59:36 +01:00
da96b4b973 Adjusted the encoder type naming to that of the new NICOS class
(https://forge.frm2.tum.de/review/c/frm2/nicos/nicos/+/35285)
2024-12-11 09:57:59 +01:00
61087d2e44 Adjusted the encoder type naming to that of the new NICOS class
(https://forge.frm2.tum.de/review/c/frm2/nicos/nicos/+/35285)
2024-12-11 09:49:17 +01:00
26754e608d Fixed a bug which resulted in motorStatusProblem not being set properly. 2024-12-10 09:09:02 +01:00
a866023957 Various improvements:
- Moved the encoder type PV to sinqMotor
- Unified the PV naming scheme
- Removed the IsEnabled function - this status is read out from the
parameter library (motorEnableRBV_)
2024-12-09 11:14:19 +01:00
8008ece919 Fixed typo
Conflicts:
2024-12-06 08:37:12 +01:00
dbe031ca79 Reworked documentation after discussion with Electronics on 04.12.2024 2024-12-06 08:35:43 +01:00
be9f3b0d76 Reworked documentation after discussion with Electronics on 04.12.2024 2024-12-05 11:14:39 +01:00
86006e408a Prototype for version 0.3 2024-12-04 13:38:13 +01:00
6656841a01 Added some flags for NICOS and refactored some records from pmacv3 to
sinqMotor
2024-11-29 14:54:54 +01:00
682325de7d Substantial rRework of 0.2.0 after the CAMEA test showed multiple
problems. Also improved the documentation.
2024-11-26 16:51:12 +01:00
97e80814e3 Merge branch 'cleaning' into 'main'
small cleaning changes

See merge request sinq-epics-modules/sinqmotor!1
2024-11-21 15:27:28 +01:00
a4943f3fe4 small cleaning changes 2024-11-20 16:39:45 +01:00
5502c39219 After reimplementing the pmacV3 driver using the sinqMotor parent class,
quite some changes have accumulated. Besides various code changes, especially the documentation has
been improved.
2024-11-20 11:29:20 +01:00
89bbbedaee Fixed CI pipeline issues. 2024-11-15 16:20:57 +01:00
2a95f82c47 Update README.md 2024-11-15 12:12:15 +01:00
a4501b4517 Moved shared functionality from the pmacV3 driver into this library. 2024-11-15 12:09:49 +01:00
8db787367d Update README.md 2024-11-14 16:21:46 +01:00
893badbada Amended README.md locally 2024-11-14 16:18:39 +01:00
df698554c0 Adjusted the readme and renamed some bits in sinqController. 2024-11-14 16:14:07 +01:00
9 changed files with 1772 additions and 102 deletions

View File

@ -17,6 +17,10 @@ SOURCES += src/sinqController.cpp
HEADERS += src/sinqAxis.h
HEADERS += src/sinqController.h
USR_CFLAGS += -Wall -Wextra # -Werror
# Store the record files
TEMPLATES += db/asynRecord.db
TEMPLATES += db/sinqMotor.db
USR_CFLAGS += -Wall -Wextra -Weffc++ -Wunused-result # -Werror
# MISCS would be the place to keep the stream device template files

186
README.md
View File

@ -4,35 +4,197 @@
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 functionalities.
## Features
## User guide
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-file. All of these functions can be overwritten manually if e.g. a completely different implementation of `poll` is required
### Architecture of EPICS motor drivers at SINQ
### sinqController
As mentioned before, the asyn-framework offers two base classes `asynMotorAxis` and `asynMotorController`. At SINQ, we extend those classes by two children classes `sinqAxis` and `sinqController` which are not complete drivers on their own, but serve as an additional framework for writing drivers. The concrete drivers are then created as separated libraries, an example is the pmacv3-driver: https://git.psi.ch/sinq-epics-modules/pmacv3
The full inheritance chain for two different motor drivers "a" and "b" looks for a 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 version management is SemVer-compatible (https://semver.org/lang/de/) in order to ensure compatibility.
For example, if driver "a" depends on version 2.1.0 of "sinqMotor", then it is safe to use version 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 pmacv3, 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
< mcu1.cmd
< mcu2.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:
```
# Define some needed parameters (they can be safely overwritten in e.g. mcu2.cmd)
epicsEnvSet("NAME","mcu1")
epicsEnvSet("ASYN_PORT","p$(NAME)")
# Define the IP adress of the controller
drvAsynIPPortConfigure("$(ASYN_PORT)","172.28.101.24:1025")
# Create the controller object in EPICS. The function "pmacv3Controller" is
# provided by loading the shared library pmacv3 earlier.
pmacv3Controller("$(NAME)","$(ASYN_PORT)",8,0.05,1,0.05);
# Create four axes objects on slots 1, 2, 3 and 5 of the controller.
pmacv3Axis("$(NAME)",1);
pmacv3Axis("$(NAME)",2);
pmacv3Axis("$(NAME)",3);
pmacv3Axis("$(NAME)",5);
# Create some general PVs of an asynRecord, substituting the macro P by concatenating INSTR and NAME and PORT by ASYN_PORT.
dbLoadRecords("$(sinqMotor_DB)/asynRecord.db","P=$(INSTR)$(NAME),PORT=$(ASYN_PORT)")
# Create PVs provided by the sinqMotor database template. This template is parametrized by the substitution file "mcu1.substitutions" (see below)
epicsEnvSet("SINQDBPATH","$(sinqMotor_DB)/sinqMotor.db")
dbLoadTemplate("$(TOP)/mcu1.substitutions", "INSTR=$(INSTR)$(NAME):,CONTROLLER=$(NAME)")
# Create PVs specific for pmacv3. Again, we load a database template and parametrize it with the substitution file "mcu1.substitutions"
epicsEnvSet("SINQDBPATH","$(pmacv3_DB)/pmacv3.db")
dbLoadTemplate("$(TOP)/mcu1.substitutions", "INSTR=$(INSTR)$(NAME):,CONTROLLER=$(NAME)")
```
### 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 }
{ 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 }
}
```
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, NAME 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](#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.
- `MSGTEXTSIZE`: Buffer size for the motor message record in characters
- `ENABLEMOVWATCHDOG`: Sets `setWatchdogEnabled` during IOC startup to the given value.
- `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).
- `CANSETSPEED`: If set to 1, the motor speed can be modified by the user.
### 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.
## 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
- `errMsgCouldNotParseResponse`: 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.
- `errMsgCouldNotParseResponse`: Write a standardized message if parsing a device response failed
- `paramLibAccessFailed`: Write a standardized message if accessing the parameter library failed
### sinqAxis
#### sinqAxis
- `atFirstPoll`: This function is executed once before the first poll. If it returns anything but `asynSuccess`, it retries.
- `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.
- `enable`: This function is called if the "Enable" PV from db/sinqMotor.db is set. 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 absolute target position in the parameter library and then calls `doHome`. The target position is assumed to be the high limit, if the distance of the current position to it is larger than that to the low limit, and the low limit otherwise.
- `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`:
- Try to execute `atFirstPoll` once (and retry, if that failed)
- Try to execute `atFirstPoll` once during the lifetime of the IOC (and retry, if that failed)
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`
- `doPoll`: This is an empty function which should be overwritten by concrete driver implementations.
- `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.
## Versioning
### 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`, where X.X is the name of your version. To create a new tag, use `git tag -a X.X`.
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
### How to build it
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 run `make install -f Makefile` from the terminal.
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 run `make install` from the terminal.
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.
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.`

9
db/asynRecord.db Executable file
View File

@ -0,0 +1,9 @@
record(asyn,"$(P)$(NAME)")
{
field(DTYP,"asynRecordDevice")
field(PORT,"$(PORT)")
field(ADDR,"0")
field(OMAX,"80")
field(IMAX,"80")
}

239
db/sinqMotor.db Executable file
View File

@ -0,0 +1,239 @@
# The main asyn motor record. Some fields are populated from the substitution
# files via macros:
# - INSTR: Name of the instrument, e.g. "SQ:SINQTEST:"
# - M: Name of the motor in EPICS, e.g. "lin1"
# - DESC: Short description of the motor. If not given, this is equal to M
# - DIR: This value is usually set to "Pos". If the motor axis direction
# should be inverted, this value can be set to "Neg"
# - CONTROLLER: Name of the motor controller, e.g. "mcu1"
# - AXIS: Number of the axis, e.g. "1"
# - MRES: Motor record resolution. See the README.md for a detailed discussion
# - EGU: Engineering units. In case of a rotary axis, this is "degree", in
# case of a linear axis this is "mm".
record(motor,"$(INSTR)$(M)")
{
field(DESC,"$(DESC=$(M))")
field(DTYP,"asynMotor")
field(DIR,"$(DIR=Pos)")
field(OUT,"@asyn($(CONTROLLER),$(AXIS))")
field(MRES,"$(MRES)")
field(EGU,"$(EGU)")
field(INIT,"")
field(PINI, "NO")
field(TWV,"1")
field(RTRY, "0")
}
# This record forwards the motor record resolution MRES to the parameter library
# entry "MOTOR_REC_RESOLUTION" (solution from https://epics.anl.gov/tech-talk/2020/msg00378.php)
# The value of MRES is needed inside the driver for various calculations (e.g.
# for calculating the estimated time of arrival inside the watchdog).
record(ao,"$(INSTR)$(M):RecResolution") {
field(DESC, "$(M) resolution")
field(DOL, "$(INSTR)$(M).MRES CP MS")
field(OMSL, "closed_loop")
field(DTYP, "asynFloat64")
field(OUT, "@asyn($(CONTROLLER),$(AXIS)) MOTOR_REC_RESOLUTION")
}
# This record contains messages from the driver (usually error messages).
# The macro MSGTEXTSIZE can be used to set the maximum length of the message.
# if not provided, a default value of 200 is used.
# This record is coupled to the parameter library via motorMessageText_ -> MOTOR_MESSAGE_TEXT.
record(waveform, "$(INSTR)$(M)-MsgTxt") {
field(DTYP, "asynOctetRead")
field(INP, "@asyn($(CONTROLLER),$(AXIS),1) MOTOR_MESSAGE_TEXT")
field(FTVL, "CHAR")
field(NELM, "$(MSGTEXTSIZE=200)") # Should be the same as MAXBUF in the driver code
field(SCAN, "I/O Intr")
}
# User-writable switch which disables the motor for an input of zero and enables
# it otherwise. Some motors can't be disabled in certain states (e.g. during
# movement). This behaviour has to be implemented inside the driver.
# This record is coupled to the parameter library via motorEnable_ -> MOTOR_ENABLE.
record(longout, "$(INSTR)$(M):Enable") {
field(DTYP, "asynInt32")
field(OUT, "@asyn($(CONTROLLER),$(AXIS),1) MOTOR_ENABLE")
field(PINI, "NO")
}
# Readback value which returns 1 if the motor is disabled and 0 otherwise.
# This record is coupled to the parameter library via motorEnableRBV_ -> MOTOR_ENABLE_RBV.
record(longin, "$(INSTR)$(M):EnableRBV") {
field(DTYP, "asynInt32")
field(INP, "@asyn($(CONTROLLER),$(AXIS),1) MOTOR_ENABLE_RBV")
field(PINI, "NO")
field(SCAN, "I/O Intr")
}
# Some (older) motors cannot be disabled. This property has to be specified in
# the driver by setting the corresponding parameter library entry motorCanDisable_
# to 0 (its default value is 1).
# This record is coupled to the parameter library via motorCanDisable_ -> MOTOR_CAN_DISABLE.
record(longin, "$(INSTR)$(M):CanDisable") {
field(DTYP, "asynInt32")
field(INP, "@asyn($(CONTROLLER),$(AXIS),1) MOTOR_CAN_DISABLE")
field(PINI, "NO")
field(SCAN, "I/O Intr")
}
# For some motors, the user might be allowed to adjust the speed within the
# limits specified in the motor record as VBAS and VMAX. This functionality can
# be enabled by setting CANSETSPEED to 1. It is disabled by default.
# This record is coupled to the parameter library via motorCanSetSpeed_ -> MOTOR_CAN_SET_SPEED.
record(longout, "$(INSTR)$(M):CanSetSpeed") {
field(DTYP, "asynInt32")
field(OUT, "@asyn($(CONTROLLER),$(AXIS),1) MOTOR_CAN_SET_SPEED")
field(PINI, "YES")
field(ASG, "READONLY") # Field is initialized during IOC startup
field(VAL, "$(CANSETSPEED=0)")
}
# 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.
record(longout, "$(INSTR)$(M):EnableMovWatchdog") {
field(DTYP, "asynInt32")
field(OUT, "@asyn($(CONTROLLER),$(AXIS),1) MOTOR_ENABLE_MOV_WATCHDOG")
field(PINI, "YES")
field(VAL, "$(ENABLEMOVWATCHDOG=0)")
}
# For modern controllers, the high and low limits of the axis are read out
# directly from the hardware. However, since the axis might slightly
# "overshoot" when moving to a position next to the limits, the hardware might
# go into a "limits hit" error state. To prevent this, this value allows adding
# a small offset in EGU, which is subtracted from the high limit and added to the
# low limit.
# This record is coupled to the parameter library via motorLimitsOffset_ -> MOTOR_LIMITS_OFFSET.
record(ao, "$(INSTR)$(M):LimitsOffset") {
field(DTYP, "asynFloat64")
field(OUT, "@asyn($(CONTROLLER),$(AXIS),1) MOTOR_LIMITS_OFFSET")
field(PINI, "YES")
field(ASG, "READONLY") # Field is initialized during IOC startup
field(VAL, "$(LIMITSOFFSET=0)")
}
# This record pair reads the parameter library value for "motorHighLimitFromDriver_"
# and pushes it to the motor record field "DHLM". This can be used to read limits
# from the hardware and correspondingly update the motor record from the driver.
# The implementation strategy is taken from https://epics.anl.gov/tech-talk/2022/msg00464.php.
# This record is coupled to the parameter library via motorHighLimitFromDriver_ -> MOTOR_HIGH_LIMIT_FROM_DRIVER.
record(ai, "$(INSTR)$(M):DHLM_RBV")
{
field(DTYP, "asynFloat64")
field(INP, "@asyn($(CONTROLLER),$(AXIS)) MOTOR_HIGH_LIMIT_FROM_DRIVER")
field(SCAN, "I/O Intr")
field(FLNK, "$(INSTR)$(M):PushDHLM2Field")
}
record(ao, "$(INSTR)$(M):PushDHLM2Field") {
field(DOL, "$(INSTR)$(M):DHLM_RBV CP")
field(OUT, "$(INSTR)$(M).DHLM")
field(OMSL, "closed_loop")
}
# This record pair reads the parameter library value for "motorLowLimitFromDriver_"
# and pushes it to the motor record field "DLLM". This can be used to read limits
# from the hardware and correspondingly update the motor record from the driver.
# The implementation strategy is taken from https://epics.anl.gov/tech-talk/2022/msg00464.php.
# This record is coupled to the parameter library via motorLowLimitFromDriver_ -> MOTOR_LOW_LIMIT_FROM_DRIVER.
record(ai, "$(INSTR)$(M):DLLM_RBV")
{
field(DTYP, "asynFloat64")
field(INP, "@asyn($(CONTROLLER),$(AXIS)) MOTOR_LOW_LIMIT_FROM_DRIVER")
field(SCAN, "I/O Intr")
field(FLNK, "$(INSTR)$(M):PushDLLM2Field")
}
record(ao, "$(INSTR)$(M):PushDLLM2Field") {
field(DOL, "$(INSTR)$(M):DLLM_RBV CP")
field(OUT, "$(INSTR)$(M).DLLM")
field(OMSL, "closed_loop")
}
# This record pair reads the parameter library value for "motorVeloFromDriver_"
# and pushes it to the motor record field "VELO". This can be used to read the speed value
# from the hardware and correspondingly update the motor record from the driver.
# The implementation strategy is taken from https://epics.anl.gov/tech-talk/2022/msg00464.php.
# This record is coupled to the parameter library via motorVeloFromDriver_ -> MOTOR_VELO_FROM_DRIVER.
record(ai, "$(INSTR)$(M):VELO_RBV")
{
field(DTYP, "asynFloat64")
field(INP, "@asyn($(CONTROLLER),$(AXIS)) MOTOR_VELO_FROM_DRIVER")
field(SCAN, "I/O Intr")
field(FLNK, "$(INSTR)$(M):PushVELO2Field")
}
record(ao, "$(INSTR)$(M):PushVELO2Field") {
field(DOL, "$(INSTR)$(M):VELO_RBV CP")
field(OUT, "$(INSTR)$(M).VELO")
field(OMSL, "closed_loop")
}
# This record pair reads the parameter library value for "motorVbasFromDriver_"
# and pushes it to the motor record field "VBAS". This can be used to read the lower speed limit
# from the hardware and correspondingly update the motor record from the driver.
# The implementation strategy is taken from https://epics.anl.gov/tech-talk/2022/msg00464.php.
# This record is coupled to the parameter library via motorVbasFromDriver_ -> MOTOR_VBAS_FROM_DRIVER.
record(ai, "$(INSTR)$(M):VBAS_RBV")
{
field(DTYP, "asynFloat64")
field(INP, "@asyn($(CONTROLLER),$(AXIS)) MOTOR_VBAS_FROM_DRIVER")
field(SCAN, "I/O Intr")
field(FLNK, "$(INSTR)$(M):PushVBAS2Field")
}
record(ao, "$(INSTR)$(M):PushVBAS2Field") {
field(DOL, "$(INSTR)$(M):VBAS_RBV CP")
field(OUT, "$(INSTR)$(M).VBAS")
field(OMSL, "closed_loop")
}
# This record pair reads the parameter library value for "motorVmaxFromDriver_"
# and pushes it to the motor record field "VMAX". This can be used to read the upper speed limit
# from the hardware and correspondingly update the motor record from the driver.
# The implementation strategy is taken from https://epics.anl.gov/tech-talk/2022/msg00464.php.
# This record is coupled to the parameter library via motorVmaxFromDriver_ -> MOTOR_VMAX_FROM_DRIVER.
record(ai, "$(INSTR)$(M):VMAX_RBV")
{
field(DTYP, "asynFloat64")
field(INP, "@asyn($(CONTROLLER),$(AXIS)) MOTOR_VMAX_FROM_DRIVER")
field(SCAN, "I/O Intr")
field(FLNK, "$(INSTR)$(M):PushVMAX2Field")
}
record(ao, "$(INSTR)$(M):PushVMAX2Field") {
field(DOL, "$(INSTR)$(M):VMAX_RBV CP")
field(OUT, "$(INSTR)$(M).VMAX")
field(OMSL, "closed_loop")
}
# This record pair reads the parameter library value for "motorAcclFromDriver_"
# and pushes it to the motor record field "ACCL". This can be used to read the acceleration
# from the hardware and correspondingly update the motor record from the driver.
# The implementation strategy is taken from https://epics.anl.gov/tech-talk/2022/msg00464.php.
# This record is coupled to the parameter library via motorAcclFromDriver_ -> MOTOR_ACCL_FROM_DRIVER.
record(ai, "$(INSTR)$(M):ACCL_RBV")
{
field(DTYP, "asynFloat64")
field(INP, "@asyn($(CONTROLLER),$(AXIS)) MOTOR_ACCL_FROM_DRIVER")
field(SCAN, "I/O Intr")
field(FLNK, "$(INSTR)$(M):PushACCL2Field")
}
record(ao, "$(INSTR)$(M):PushACCL2Field") {
field(DOL, "$(INSTR)$(M):ACCL_RBV CP")
field(OUT, "$(INSTR)$(M).ACCL")
field(OMSL, "closed_loop")
}
# Read out the encoder type in human-readable form. The output numbers are ASCII
# codes and can be converted to chars in order to get the encoder type.
# EPICS prepends the ASCII code with 80
# The following encoder types are defined:
# - "Absolute encoder" (array 80 65 98 115 111 108 117 116 101 32 101 110 99 111 100 101 114)
# - "Incremental encoder" (array 80 73 110 99 114 101 109 101 110 116 97 108 32 101 110 99 111 100 101 114)
# This record is coupled to the parameter library via encoderType -> ENCODER_TYPE.
record(waveform, "$(INSTR)$(M):EncoderType") {
field(DTYP, "asynOctetRead")
field(INP, "@asyn($(CONTROLLER),$(AXIS),1) ENCODER_TYPE")
field(FTVL, "CHAR")
field(NELM, "80")
field(SCAN, "I/O Intr")
}

View File

@ -1,15 +1,101 @@
#include "sinqAxis.h"
#include "sinqController.h"
#include <math.h>
#include <unistd.h>
sinqAxis::sinqAxis(class sinqController *pC, int axis)
: asynMotorAxis((asynMotorController *)pC, axis), pC_(pC) {
sinqAxis::sinqAxis(class sinqController *pC, int axisNo)
: asynMotorAxis((asynMotorController *)pC, axisNo), pC_(pC) {
asynStatus status = asynSuccess;
bool initial_poll_ = true;
int init_poll_counter_ = 0;
initial_poll_ = true;
watchdogMovActive_ = false;
init_poll_counter_ = 0;
scaleMovTimeout_ = 2.0;
offsetMovTimeout_ = 30;
// Motor is assumed to be enabled
status = pC_->setIntegerParam(axisNo_, pC_->motorEnableRBV_, 1);
if (status != asynSuccess) {
asynPrint(
pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (setting a parameter value failed "
"with %s)\n. Terminating IOC",
__PRETTY_FUNCTION__, __LINE__, pC_->stringifyAsynStatus(status));
exit(-1);
}
// By default, motors cannot be disabled
status = pC_->setIntegerParam(axisNo_, pC_->motorCanDisable_, 0);
if (status != asynSuccess) {
asynPrint(
pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (setting a parameter value failed "
"with %s)\n. Terminating IOC",
__PRETTY_FUNCTION__, __LINE__, pC_->stringifyAsynStatus(status));
exit(-1);
}
// Provide a default value for the motor position.
status = pC_->setDoubleParam(axisNo_, pC_->motorPosition_, 0.0);
if (status != asynSuccess) {
asynPrint(
pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (setting a parameter value failed "
"with %s)\n. Terminating IOC",
__PRETTY_FUNCTION__, __LINE__, pC_->stringifyAsynStatus(status));
exit(-1);
}
// We assume that the motor has no status problems initially
status = pC_->setIntegerParam(axisNo_, pC_->motorStatusProblem_, 0);
if (status != asynSuccess) {
asynPrint(
pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (setting a parameter value failed "
"with %s)\n. Terminating IOC",
__PRETTY_FUNCTION__, __LINE__, pC_->stringifyAsynStatus(status));
exit(-1);
}
}
asynStatus sinqAxis::atFirstPoll() { return asynSuccess; }
asynStatus sinqAxis::atFirstPoll() {
asynStatus status = asynSuccess;
int variableSpeed = 0;
status =
pC_->getIntegerParam(axisNo_, pC_->motorCanSetSpeed_, &variableSpeed);
if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorCanSetSpeed_",
__PRETTY_FUNCTION__, __LINE__);
}
if (variableSpeed == 1) {
status = pC_->setDoubleParam(axisNo_, pC_->motorVbasFromDriver_, 0.0);
if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorVbasFromDriver_",
__PRETTY_FUNCTION__, __LINE__);
}
status = pC_->setDoubleParam(axisNo_, pC_->motorVmaxFromDriver_,
1000000000.0);
if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorVmaxFromDriver_",
__PRETTY_FUNCTION__, __LINE__);
}
} else {
status = pC_->setDoubleParam(axisNo_, pC_->motorVbasFromDriver_, 0.0);
if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorVbasFromDriver_",
__PRETTY_FUNCTION__, __LINE__);
}
status = pC_->setDoubleParam(axisNo_, pC_->motorVmaxFromDriver_, 0.0);
if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorVmaxFromDriver_",
__PRETTY_FUNCTION__, __LINE__);
}
}
return status;
}
asynStatus sinqAxis::poll(bool *moving) {
// Local variable declaration
@ -19,12 +105,13 @@ asynStatus sinqAxis::poll(bool *moving) {
// =========================================================================
// If this poll is the initial poll, check if the parameter library has
// already been initialized. If not, force EPCIS to repeat the poll until
// already been initialized. If not, force EPICS to repeat the poll until
// the initialization is complete (or until a timeout is reached). Once the
// parameter library has been initialized, read configuration data from the
// motor controller into it.
if (initial_poll_) {
poll_status = atFirstPoll();
if (poll_status == asynSuccess) {
initial_poll_ = false;
} else {
@ -33,7 +120,7 @@ asynStatus sinqAxis::poll(bool *moving) {
if (init_poll_counter_ % 10 == 0) {
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nRunning function 'atFirstPoll' "
"failed %d times with error %s.",
"failed %d times with error %s.\n",
__PRETTY_FUNCTION__, __LINE__, init_poll_counter_,
pC_->stringifyAsynStatus(poll_status));
}
@ -44,27 +131,58 @@ asynStatus sinqAxis::poll(bool *moving) {
}
}
/*
At the beginning of the poll, it is assumed that the axis has no status
problems and therefore all error indicators are reset. This does not affect
the PVs until callParamCallbacks has been called!
The motorStatusProblem_ field changes the motor record fields SEVR and STAT.
*/
pl_status = setIntegerParam(pC_->motorStatusProblem_, false);
if (pl_status != asynSuccess) {
pC_->paramLibAccessFailed(pl_status, "motorStatusProblem_",
__PRETTY_FUNCTION__, __LINE__);
}
pl_status = setIntegerParam(pC_->motorStatusCommsError_, false);
if (pl_status != asynSuccess) {
pC_->paramLibAccessFailed(pl_status, "motorStatusCommsError_",
__PRETTY_FUNCTION__, __LINE__);
}
pl_status = setStringParam(pC_->motorMessageText_, "");
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorMessageText_",
__PRETTY_FUNCTION__, __LINE__);
}
// The poll function is just a wrapper around doPoll and
// handles mainly the callParamCallbacks() function. This wrapper is used
// to make sure callParamCallbacks() is called in case of a premature
// return.
poll_status = doPoll(moving);
// If the poll status is ok, reset the error indicators in the parameter
// library
if (poll_status == asynSuccess) {
pl_status = setIntegerParam(pC_->motorStatusProblem_, false);
// The poll did not succeed: Something went wrong and the motor has a status
// problem.
if (poll_status != asynSuccess) {
pl_status = setIntegerParam(pC_->motorStatusProblem_, true);
if (pl_status != asynSuccess) {
pC_->paramLibAccessFailed(pl_status, "motorStatusProblem_",
__PRETTY_FUNCTION__, __LINE__);
}
pl_status = setIntegerParam(pC_->motorStatusCommsError_, false);
if (pl_status != asynSuccess) {
pC_->paramLibAccessFailed(pl_status, "motorStatusCommsError_",
__PRETTY_FUNCTION__, __LINE__);
}
}
pl_status = setStringParam(pC_->motorMessageText_, "");
// Check and update the watchdog
if (checkMovTimeoutWatchdog(*moving) != asynSuccess) {
return asynError;
}
if (pl_status != asynSuccess) {
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFunction isEnabled failed with %s.\n",
__PRETTY_FUNCTION__, __LINE__,
pC_->stringifyAsynStatus(poll_status));
pl_status = setStringParam(pC_->motorMessageText_,
"Could not check whether the motor is "
"enabled or not. Please call the support");
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorMessageText_",
__PRETTY_FUNCTION__, __LINE__);
@ -88,4 +206,353 @@ asynStatus sinqAxis::poll(bool *moving) {
return poll_status;
}
asynStatus sinqAxis::doPoll(bool *moving) { return asynSuccess; }
asynStatus sinqAxis::doPoll(bool *moving) { return asynSuccess; }
asynStatus sinqAxis::move(double position, int relative, double minVelocity,
double maxVelocity, double acceleration) {
double targetPosition = 0.0;
// Status of parameter library operations
asynStatus pl_status = asynSuccess;
// =========================================================================
// Calculate the (absolute) target position
if (relative) {
double motorPosition = 0.0;
pl_status =
pC_->getDoubleParam(axisNo_, pC_->motorPosition_, &motorPosition);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorPosition_",
__PRETTY_FUNCTION__, __LINE__);
}
targetPosition = position + motorPosition;
} else {
targetPosition = position;
}
// Set the target position
pl_status = setDoubleParam(pC_->motorTargetPosition_, targetPosition);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorTargetPosition_",
__PRETTY_FUNCTION__, __LINE__);
}
return doMove(position, relative, minVelocity, maxVelocity, acceleration);
}
asynStatus sinqAxis::doMove(double position, int relative, double minVelocity,
double maxVelocity, double acceleration) {
return asynSuccess;
}
asynStatus sinqAxis::home(double minVelocity, double maxVelocity,
double acceleration, int forwards) {
double targetPosition = 0.0;
double position = 0.0;
double highLimit = 0.0;
double lowLimit = 0.0;
// Status of parameter library operations
asynStatus pl_status = asynSuccess;
// =========================================================================
pl_status = pC_->getDoubleParam(axisNo_, pC_->motorPosition_, &position);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorPosition_",
__PRETTY_FUNCTION__, __LINE__);
}
pl_status = pC_->getDoubleParam(axisNo_, pC_->motorHighLimit_, &highLimit);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorHighLimit_",
__PRETTY_FUNCTION__, __LINE__);
}
pl_status = pC_->getDoubleParam(axisNo_, pC_->motorLowLimit_, &lowLimit);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorLowLimit_",
__PRETTY_FUNCTION__, __LINE__);
}
if (std::fabs(position - highLimit) > std::fabs(position - lowLimit)) {
targetPosition = highLimit;
} else {
targetPosition = lowLimit;
}
// Set the target position
pl_status = setDoubleParam(pC_->motorTargetPosition_, targetPosition);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorTargetPosition_",
__PRETTY_FUNCTION__, __LINE__);
}
return doHome(minVelocity, maxVelocity, acceleration, forwards);
}
asynStatus sinqAxis::doHome(double minVelocity, double maxVelocity,
double acceleration, int forwards) {
return asynSuccess;
}
asynStatus sinqAxis::enable(bool on) { return asynSuccess; }
asynStatus sinqAxis::setVeloFields(double velo, double vbas, double vmax) {
asynStatus status = asynSuccess;
int variableSpeed = 0;
// Can the speed of the motor be varied?
status =
pC_->getIntegerParam(axisNo_, pC_->motorCanSetSpeed_, &variableSpeed);
if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorCanSetSpeed_",
__PRETTY_FUNCTION__, __LINE__);
}
if (variableSpeed == 1) {
// Check the inputs and create corresponding error messages
if (vbas > vmax) {
asynPrint(pC_->lowLevelPortUser_, ASYN_TRACE_ERROR,
"%s => line %d:\nLower speed limit vbas=%lf must not be "
"smaller than upper limit vmax=%lf.\n",
__PRETTY_FUNCTION__, __LINE__, vbas, vmax);
status = setStringParam(
pC_->motorMessageText_,
"Lower speed limit must not be smaller than upper speed limit");
if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorMessageText_",
__PRETTY_FUNCTION__, __LINE__);
}
return asynError;
}
if (velo < vbas || velo > vmax) {
asynPrint(pC_->lowLevelPortUser_, ASYN_TRACE_ERROR,
"%s => line %d:\nActual speed velo=%lf must be between "
"lower limit vbas=%lf and upper limit vmx=%lf.\n",
__PRETTY_FUNCTION__, __LINE__, velo, vbas, vmax);
status = setStringParam(pC_->motorMessageText_,
"Speed is not inside limits");
if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorMessageText_",
__PRETTY_FUNCTION__, __LINE__);
}
return asynError;
}
status = pC_->setDoubleParam(axisNo_, pC_->motorVbasFromDriver_, vbas);
if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorVbasFromDriver_",
__PRETTY_FUNCTION__, __LINE__);
}
status = pC_->setDoubleParam(axisNo_, pC_->motorVeloFromDriver_, velo);
if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorVeloFromDriver_",
__PRETTY_FUNCTION__, __LINE__);
}
status = pC_->setDoubleParam(axisNo_, pC_->motorVmaxFromDriver_, vmax);
if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorVmaxFromDriver_",
__PRETTY_FUNCTION__, __LINE__);
}
} else {
// Set minimum and maximum speed equal to the set speed
status = pC_->setDoubleParam(axisNo_, pC_->motorVbasFromDriver_, velo);
if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorVbasFromDriver_",
__PRETTY_FUNCTION__, __LINE__);
}
status = pC_->setDoubleParam(axisNo_, pC_->motorVeloFromDriver_, velo);
if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorVeloFromDriver_",
__PRETTY_FUNCTION__, __LINE__);
}
status = pC_->setDoubleParam(axisNo_, pC_->motorVmaxFromDriver_, velo);
if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorVmaxFromDriver_",
__PRETTY_FUNCTION__, __LINE__);
}
}
return status;
}
asynStatus sinqAxis::setAcclField(double accl) {
if (accl <= 0.0) {
return asynError;
}
asynStatus status =
pC_->setDoubleParam(axisNo_, pC_->motorAcclFromDriver_, accl);
if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorAcclFromDriver_",
__PRETTY_FUNCTION__, __LINE__);
}
return status;
}
asynStatus sinqAxis::setWatchdogEnabled(bool enable) {
return pC_->setIntegerParam(axisNo_, pC_->motorEnableMovWatchdog_, enable);
}
asynStatus sinqAxis::startMovTimeoutWatchdog() {
asynStatus pl_status;
int enableMovWatchdog = 0;
pl_status = pC_->getIntegerParam(axisNo_, pC_->motorEnableMovWatchdog_,
&enableMovWatchdog);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorEnableMovWatchdog_",
__PRETTY_FUNCTION__, __LINE__);
}
if (enableMovWatchdog == 1) {
// These parameters are only needed in this branch
double motorPosition = 0.0;
double motorPositionRec = 0.0;
double motorTargetPositionRec = 0.0;
double motorTargetPosition = 0.0;
double motorVelocity = 0.0;
double motorVelocityRec = 0.0;
double motorAccel = 0.0;
double motorAccelRec = 0.0;
double motorRecResolution = 0.0;
time_t timeContSpeed = 0;
time_t timeAccel = 0;
// Activate the watchdog
watchdogMovActive_ = true;
/*
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!
*/
pl_status = pC_->getDoubleParam(axisNo_, pC_->motorRecResolution_,
&motorRecResolution);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorRecResolution_",
__PRETTY_FUNCTION__, __LINE__);
}
pl_status = pC_->getDoubleParam(axisNo_, pC_->motorPosition_,
&motorPositionRec);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorPosition",
__PRETTY_FUNCTION__, __LINE__);
}
motorPosition = motorPositionRec * motorRecResolution;
/*
We use motorVelocity, which corresponds to the record field VELO.
From https://epics.anl.gov/docs/APS2015/14-Motor-Record.pdf:
* VELO = motorVelocity_ = Slew velocity
* VBAS = motorVelBase_ = Only used for stepper motors to minimize
resonance.
As documented in
https://epics.anl.gov/docs/APS2015/17-Motor-Driver.pdf, the
following relations apply: motorVelBase = VBAS / MRES motorVelocity
= VELO / MRES motorAccel = (motorVelocity - motorVelBase) / ACCL
Therefore, we need to correct the values from the parameter library.
*/
// Read the velocity
pl_status = pC_->getDoubleParam(axisNo_, pC_->motorVelocity_,
&motorVelocityRec);
// Only calculate timeContSpeed if the motorVelocity has been populated
// with a sensible value (e.g. > 0)
if (pl_status == asynSuccess && motorVelocityRec > 0.0) {
// Convert back to the value in the VELO field
motorVelocity = motorVelocityRec * motorRecResolution;
pl_status = pC_->getDoubleParam(axisNo_, pC_->motorTargetPosition_,
&motorTargetPositionRec);
motorTargetPosition = motorTargetPositionRec * motorRecResolution;
if (pl_status == asynSuccess) {
timeContSpeed =
std::ceil(std::fabs(motorTargetPosition - motorPosition) /
motorVelocity);
}
}
pl_status =
pC_->getDoubleParam(axisNo_, pC_->motorAccel_, &motorAccelRec);
if (pl_status == asynSuccess && motorVelocityRec > 0.0 &&
motorAccelRec > 0.0) {
// Convert back to the value in the ACCL field
motorAccel = motorVelocityRec / motorAccelRec;
// Calculate the time
timeAccel = 2 * std::ceil(motorVelocity / motorAccel);
}
// Calculate the expected arrival time
expectedArrivalTime_ =
time(NULL) + offsetMovTimeout_ +
scaleMovTimeout_ * (timeContSpeed + 2 * timeAccel);
} else {
watchdogMovActive_ = false;
}
return asynSuccess;
}
asynStatus sinqAxis::checkMovTimeoutWatchdog(bool moving) {
asynStatus pl_status;
int enableMovWatchdog = 0;
pl_status = pC_->getIntegerParam(axisNo_, pC_->motorEnableMovWatchdog_,
&enableMovWatchdog);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorEnableMovWatchdog_",
__PRETTY_FUNCTION__, __LINE__);
}
// Not moving or watchdog not active / enabled
if (enableMovWatchdog == 0 || !moving || !watchdogMovActive_) {
watchdogMovActive_ = false;
return asynSuccess;
}
// Check if the expected time of arrival has been exceeded.
if (expectedArrivalTime_ < time(NULL)) {
// Check the watchdog
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nAxis %d exceeded the expected arrival time "
"%ld (current time is %ld).\n",
__PRETTY_FUNCTION__, __LINE__, axisNo_, expectedArrivalTime_,
time(NULL));
pl_status = setStringParam(
pC_->motorMessageText_,
"Exceeded expected arrival time. Check if the axis is blocked.");
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorMessageText_",
__PRETTY_FUNCTION__, __LINE__);
}
pl_status = setIntegerParam(pC_->motorStatusProblem_, true);
if (pl_status != asynSuccess) {
pC_->paramLibAccessFailed(pl_status, "motorStatusProblem_",
__PRETTY_FUNCTION__, __LINE__);
}
// Even if the movement timed out, the rest of the poll should continue.
return asynSuccess;
}
return asynSuccess;
}

View File

@ -3,28 +3,276 @@ This class extends asynMotorAxis by some features used in SINQ.
Stefan Mathis, November 2024
*/
#ifndef __SINQDRIVER
#define __SINQDRIVER
#include "asynMotorAxis.h"
class epicsShareClass sinqAxis : public asynMotorAxis {
public:
sinqAxis(class sinqController *pC_, int axis);
/**
* @brief Construct a new sinqAxis object
*
* @param pC_ Pointer to the controller of the axis
* @param axis Index of the axis
*/
sinqAxis(class sinqController *pC_, int axisNo);
/**
This function is executed at the very first poll after the IOC startup. If
it returns anything else than 'asynSuccess', the function is evaluated again
after 100 ms until it succeeds. Every 10 trials a warning is emitted.
* @brief This function is executed once during the very first poll.
*
* This function is executed at the very first poll after the IOC startup.
If it returns anything else than 'asynSuccess', the function is evaluated
again after 100 ms until it succeeds. Every 10 trials a warning is emitted.
The default implementation just returns asynSuccess and is meant to be
overwritten by concrete driver implementations.
*
* @return asynStatus
*/
asynStatus atFirstPoll();
virtual asynStatus atFirstPoll();
/**
* @brief Perform some standardized operation before and after the concrete
`doPoll` implementation.
*
* Wrapper around doPoll which performs the following operations:
Before calling doPoll:
- Try to execute atFirstPoll once (and retry, if that failed)
After calling doPoll:
- Reset motorStatusProblem_, motorStatusCommsError_ and motorMessageText_ if
doPoll returned asynSuccess
- If the movement timeout watchdog has been started, check it.
- Update the parameter library entry motorEnableRBV_ by calling isEnabled.
- Run `callParamCallbacks`
- Return the status of `doPoll`
*
* @param moving Forwarded to `doPoll`.
* @return asynStatus Forward the status of `doPoll`, unless one of
the parameter library operation fails (in that case, returns the failed
operation status).
*/
asynStatus poll(bool *moving);
/**
Implementation of the "proper", device-specific poll method. This method
should be implemented by a child class of sinqAxis.
* @brief Implementation of the "proper", device-specific poll method. This
method should be implemented by a child class of sinqAxis.
*
* @param moving Should be set to true, if the axis is moving,
* and false otherwise.
* @return asynStatus
*/
asynStatus doPoll(bool *moving);
virtual asynStatus doPoll(bool *moving);
/**
* @brief Perform some standardized operation before and after the concrete
`doMove` implementation.
* Wrapper around `doMove` which calculates the (absolute) target position
and stores it in the parameter library. After that, it calls and returns
`doMove`.
*
* @param position Forwarded to `doMove`.
* @param relative Forwarded to `doMove`.
* @param minVelocity Forwarded to `doMove`.
* @param maxVelocity Forwarded to `doMove`.
* @param acceleration Forwarded to `doMove`.
* @return asynStatus Forward the status of `doMove`, unless one of
the parameter library operation fails (in that case, returns the failed
operation status).
*/
asynStatus move(double position, int relative, double minVelocity,
double maxVelocity, double acceleration);
/**
* @brief Implementation of the "proper", device-specific move method. This
method should be implemented by a child class of sinqAxis.
*
* @param position Target position `VAL` from the motor record
* @param relative Specifies, whether the target position is
relative or absolute.
* @param minVelocity Minimum velocity VMIN from the motor record
* @param maxVelocity Maximum velocity VMAX from the motor record
* @param acceleration Acceleration ACCEL from the motor record
* @return asynStatus
*/
virtual asynStatus doMove(double position, int relative, double minVelocity,
double maxVelocity, double acceleration);
/**
* @brief Perform some standardized operation before and after the concrete
`doHome` implementation.
*
* Wrapper around move which calculates the (absolute) target position and
stores it in the parameter library. The target position in a homing maneuver
is calculated as follows:
if abs(current position - high limit) > abs(current position - low limit)
{
high limit
}
else
{
low limit
}
After that, it calls and returns doHome.
*
* @param minVelocity Forwarded to `doHome`.
* @param maxVelocity Forwarded to `doHome`.
* @param acceleration Forwarded to `doHome`.
* @param forwards Forwarded to `doHome`.
* @return asynStatus Forward the status of `doHome`, unless one of
the parameter library operation fails (in that case, returns the failed
operation status).
*/
asynStatus home(double minVelocity, double maxVelocity, double acceleration,
int forwards);
/**
* @brief Implementation of the "proper", device-specific home method. This
method should be implemented by a child class of sinqAxis.
*
* @param minVelocity Minimum velocity VMIN from the motor record
* @param maxVelocity Maximum velocity VMAX from the motor record
* @param acceleration Acceleration ACCEL from the motor record
* @param forwards Is 1, if the motor record field HOMF was used
to trigger the homing, and 0, if HOMR was used.
* @return asynStatus
*/
virtual asynStatus doHome(double minVelocity, double maxVelocity,
double acceleration, int forwards);
/**
* @brief This function enables / disables an axis. It should be implemented
* by a child class of sinqAxis.
*
* @param on
* @return asynStatus
*/
virtual asynStatus enable(bool on);
/**
* @brief Populate the motor record fields VELO, VBAS and VMAX
*
* Populates the speed fields of the motor record. If the param lib
* entry motorCanSetSpeed_ (connected to the PV x:VariableSpeed) is set to
* 1, VBAS and VMAX are set to min and max respectively. Otherwise, they are
* set to val. Additionally, the speed itself is set to velo.
*
* The units of the inputs are engineering units (EGU) per second (e.g. mm/s
* if the EGU is mm).
*
* If the given configuration is invalid (min > max, velo < min, velo > max)
* and the motor is configured as a variable speed motor (param lib entry
* motorCanSetSpeed_ is 1), this function returns an asynError.
*
* @param velo Actual velocity (EGU / s)
* @param vbas Minimum allowed velocity (EGU / s)
* @param velo Maximum allowed velocity (EGU / s)
*
* @return asynStatus
*/
virtual asynStatus setVeloFields(double velo, double vbas, double vmax);
/**
* @brief Populate the ACCL field of the motor record
*
* Populates the acceleration field of the motor record with the given
* value. If accl is not positive, this function does not set the value and
* returns an asynError.
*
* The unit of the input is engineering units (EGU) per second squared (e.g.
* mm/s^2 if the EGU is mm).
*
* @param accl Actual acceleration (EGU / s^2)
* @return asynStatus
*/
virtual asynStatus setAcclField(double accl);
/**
* @brief Start the watchdog for the movement, if the watchdog is not
disabled. See the documentation of checkMovTimeoutWatchdog for more details.
*
* @return asynStatus If one of the parameter library operations
used to get the values for the timeout calculation failed, return that
status, otherwise return asynSuccess.
*/
asynStatus startMovTimeoutWatchdog();
/**
* @brief Check if the watchdog timed out
*
Manages a timeout mechanism for the movement:
If the axis is moving and the movement takes too long, create an error
message and return asynError. The watchdog is started when moving switches
from "false" to "true" and stopped when moving switches from "true" to
"false". At the watchdog start, the estimated movement time is calculated as
t = offsetMovTimeout_ + scaleMovTime_ * [timeContSpeed + 2*timeAccel]
with
timeContSpeed = abs(motorTargetPosition - motorPosition) / motorVelBase
timeAcc = motorVelBase / motorAccel
The values motorTargetPosition, motorVelBase, motorAccel and
positionAtMovementStart are taken from the parameter library. Therefore it
is necessary to populate them before using this function. If they are not
given, both speed and velocity are assumed to be infinite. This means that
timeContSpeed and/or timeAcc are set to zero. motorTargetPosition is
populated automatically when using the doMove function.
The values offsetMovTimeout_ and scaleMovTimeout_ can be set directly from
the IOC shell with the functions setScaleMovTimeout and setOffsetMovTimeout,
if sinqMotor is loaded via the "require" mechanism.
*
* @param moving Should be the "moving" status from `poll` /
`doPoll`.
* @return asynStatus Return asynError, if the watchdog timed out,
and asynSuccess otherwise.
*/
asynStatus checkMovTimeoutWatchdog(bool moving);
/**
* @brief Enable / disable the watchdog. Also available in the IOC shell
* (see "extern C" section in sinqController.cpp).
*
* If enable is set to false and the watchdog is currently running, this
* function stops it immediately.
*
* @param enabled
* @return asynStatus
*/
virtual asynStatus setWatchdogEnabled(bool enable);
/**
* @brief Set the offsetMovTimeout. Also available in the IOC shell
* (see "extern C" section in sinqController.cpp).
*
See documentation of `checkMovTimeoutWatchdog` for details.
*
* @param offsetMovTimeout Offset (in seconds)
* @return asynStatus
*/
virtual asynStatus setOffsetMovTimeout(time_t offsetMovTimeout) {
offsetMovTimeout_ = offsetMovTimeout;
return asynSuccess;
}
/**
* @brief Set the scaleMovTimeout. Also available in the IOC shell
* (see "extern C" section in sinqController.cpp).
*
See documentation of `checkMovTimeoutWatchdog` for details.
*
* @param scaleMovTimeout Scaling factor (in seconds)
* @return asynStatus
*/
virtual asynStatus setScaleMovTimeout(time_t scaleMovTimeout) {
scaleMovTimeout_ = scaleMovTimeout;
return asynSuccess;
}
friend class sinqController;
@ -32,6 +280,12 @@ class epicsShareClass sinqAxis : public asynMotorAxis {
bool initial_poll_;
int init_poll_counter_;
// Helper variables for movementTimeoutWatchdog
time_t expectedArrivalTime_;
time_t offsetMovTimeout_;
double scaleMovTimeout_;
bool watchdogMovActive_;
private:
sinqController *pC_;
};

View File

@ -1,49 +1,313 @@
/*
This class contains the necessary changes to have an additional text fields
for messages with each axis.
Code lifted from Torsten Boegershausen ESS code.
Mark Koennecke, March 2017
Added code to manage an interMessageSleep
Mark Koennecke, February 2024
*/
#include "sinqController.h"
#include "asynMotorController.h"
#include "asynOctetSyncIO.h"
#include "epicsExport.h"
#include "iocsh.h"
#include "sinqAxis.h"
#include <errlog.h>
sinqController::sinqController(const char *portName, const char *SINQPortName,
int numAxes, const int &extraParams)
sinqController::sinqController(const char *portName,
const char *ipPortConfigName, int numAxes,
double movingPollPeriod, double idlePollPeriod,
int numExtraParams)
: asynMotorController(
portName, numAxes + 1, NUM_MOTOR_DRIVER_PARAMS + extraParams,
portName,
// As described in the function documentation, an offset of 1 is
// added for better readability of the configuration.
numAxes + 1,
/*
4 Parameters are added in sinqController:
- MOTOR_MESSAGE_TEXT
- MOTOR_TARGET_POSITION
- ENABLE_AXIS
- ENABLE_AXIS_RBV
- ENABLE_MOV_WATCHDOG
- LIMITS_OFFSET
*/
NUM_MOTOR_DRIVER_PARAMS + NUM_SINQMOTOR_DRIVER_PARAMS +
numExtraParams,
0, // No additional interfaces beyond those in base class
0, // No additional callback interfaces beyond those in base class
ASYN_CANBLOCK | ASYN_MULTIDEVICE,
1, // autoconnect
0, 0) // Default priority and stack size
1, // autoconnect
0,
0) // Default priority and stack size
{
createParam(motorMessageTextString, asynParamOctet, &motorMessageText_);
// Initialization of local variables
asynStatus status = asynSuccess;
// Initialization of all member variables
lowLevelPortUser_ = nullptr;
// =========================================================================;
/*
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, &lowLevelPortUser_, NULL);
if (status != asynSuccess || lowLevelPortUser_ == nullptr) {
errlogPrintf(
"%s => line %d:\nFATAL ERROR (cannot connect to MCU controller).\n"
"Terminating IOC",
__PRETTY_FUNCTION__, __LINE__);
exit(-1);
}
// =========================================================================;
// MOTOR_MESSAGE_TEXT corresponds to the PV definition inside sinqMotor.db.
// This text is used to forward status messages to NICOS and in turn to the
// user.
status =
createParam("MOTOR_MESSAGE_TEXT", asynParamOctet, &motorMessageText_);
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);
}
// Internal parameter library entry which stores the movement target
status = createParam("MOTOR_TARGET_POSITION", asynParamFloat64,
&motorTargetPosition_);
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_ENABLE", asynParamInt32, &motorEnable_);
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_ENABLE_RBV", asynParamInt32, &motorEnableRBV_);
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_CAN_DISABLE", asynParamInt32, &motorCanDisable_);
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_CAN_SET_SPEED", asynParamInt32, &motorCanSetSpeed_);
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_LIMITS_OFFSET", asynParamFloat64,
&motorLimitsOffset_);
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);
}
/*
We need to introduce 2 new parameters in order to write the limits from the
driver to the EPICS record. See the comment in sinqController.h next to
the declaration of motorHighLimitFromDriver_.
*/
status = createParam("MOTOR_HIGH_LIMIT_FROM_DRIVER", asynParamFloat64,
&motorHighLimitFromDriver_);
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_LOW_LIMIT_FROM_DRIVER", asynParamFloat64,
&motorLowLimitFromDriver_);
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_ENABLE_MOV_WATCHDOG", asynParamInt32,
&motorEnableMovWatchdog_);
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_VELO_FROM_DRIVER", asynParamFloat64,
&motorVeloFromDriver_);
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_VBAS_FROM_DRIVER", asynParamFloat64,
&motorVbasFromDriver_);
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_VMAX_FROM_DRIVER", asynParamFloat64,
&motorVmaxFromDriver_);
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_ACCL_FROM_DRIVER", asynParamFloat64,
&motorAcclFromDriver_);
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,
"%s => line %d:\nFATAL ERROR (creating a parameter failed "
"with %s).\nTerminating IOC",
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status));
exit(-1);
}
// Poller configuration
status = startPoller(movingPollPeriod, idlePollPeriod, 1);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (starting the poller failed "
"with %s).\nTerminating IOC",
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status));
pasynOctetSyncIO->disconnect(lowLevelPortUser_);
exit(-1);
}
}
sinqController::~sinqController(void) {
/*
Cleanup of the memory allocated in the asynMotorController constructor
*/
free(this->pAxes_);
}
asynStatus sinqController::writeInt32(asynUser *pasynUser, epicsInt32 value) {
int function = pasynUser->reason;
// =====================================================================
asynMotorAxis *asynAxis = getAxis(pasynUser);
sinqAxis *axis = dynamic_cast<sinqAxis *>(asynAxis);
if (axis == nullptr) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nAxis %d is not an instance of sinqAxis",
__PRETTY_FUNCTION__, __LINE__, axis->axisNo_);
return asynError;
}
// Handle custom PVs
if (function == motorEnable_) {
return axis->enable(value != 0);
} else {
return asynMotorController::writeInt32(pasynUser, value);
}
}
asynStatus sinqController::readInt32(asynUser *pasynUser, epicsInt32 *value) {
if (pasynUser->reason == motorEnableRBV_) {
// Read out the parameter library
asynMotorAxis *asynAxis = getAxis(pasynUser);
sinqAxis *axis = dynamic_cast<sinqAxis *>(asynAxis);
if (axis == nullptr) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nAxis %d is not an instance of sinqAxis",
__PRETTY_FUNCTION__, __LINE__, axis->axisNo_);
return asynError;
}
return getIntegerParam(axis->axisNo_, motorEnableRBV_, value);
} else if (pasynUser->reason == motorCanDisable_) {
// Check if the motor can be disabled
asynMotorAxis *asynAxis = getAxis(pasynUser);
sinqAxis *axis = dynamic_cast<sinqAxis *>(asynAxis);
if (axis == nullptr) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nAxis %d is not an instance of sinqAxis",
__PRETTY_FUNCTION__, __LINE__, axis->axisNo_);
return asynError;
}
return getIntegerParam(axis->axisNo_, motorCanDisable_, value);
} else {
return asynMotorController::readInt32(pasynUser, value);
}
}
asynStatus sinqController::errMsgCouldNotParseResponse(const char *command,
const char *response,
int axisNo_,
int axisNo,
const char *functionName,
int lineNumber) {
asynStatus pl_status = asynSuccess;
asynPrint(lowLevelPortUser_, ASYN_TRACE_ERROR,
"%s => line %d:\n Could not interpret response %s for "
"%s => line %d:\nCould not interpret response '%s' for "
"command %s.\n",
functionName, lineNumber, response, command);
setStringParam(motorMessageText_,
"Could not interpret MCU response. Please "
"call the software support");
setIntegerParam(motorStatusCommsError_, 1);
pl_status = setStringParam(
motorMessageText_,
"Could not interpret MCU response. Please call the support");
if (pl_status != asynSuccess) {
return paramLibAccessFailed(pl_status, "motorMessageText_",
__PRETTY_FUNCTION__, __LINE__);
}
pl_status = setIntegerParam(motorStatusCommsError_, 1);
if (pl_status != asynSuccess) {
return paramLibAccessFailed(pl_status, "motorStatusCommsError_",
__PRETTY_FUNCTION__, __LINE__);
}
return asynError;
}
@ -53,14 +317,15 @@ asynStatus sinqController::paramLibAccessFailed(asynStatus status,
int lineNumber) {
if (status != asynSuccess) {
// Log the error message and try to propagate it
// Log the error message and try to propagate it. If propagating fails,
// there is nothing we can do here anyway.
asynPrint(lowLevelPortUser_, ASYN_TRACE_ERROR,
"%s => line %d:\n Accessing the parameter library failed for "
"parameter %s",
functionName, lineNumber, parameter);
setStringParam(
motorMessageText_,
"Accessing paramLib failed. Please call the software support.");
"parameter %s with error %s.\n",
functionName, lineNumber, parameter,
stringifyAsynStatus(status));
setStringParam(motorMessageText_,
"Accessing paramLib failed. Please call the support.");
}
return status;
@ -69,18 +334,21 @@ asynStatus sinqController::paramLibAccessFailed(asynStatus status,
// Static pointers (valid for the entire lifetime of the IOC). The number behind
// the strings gives the integer number of each variant (see also method
// stringifyAsynStatus)
const char *asynSuccessStringified = "success"; // 0
const char *asynTimeoutStringified = "timeout"; // 1
const char *asynOverflowStringified = "overflow"; // 2
const char *asynErrorStringified = "error"; // 3
const char *asynDisconnectedStringified = "disconnected"; // 4
const char *asynDisabledStringified = "disabled"; // 5
const char *asynParamAlreadyExistsStringified = "parameter already exists"; // 6
const char *asynParamNotFoundStringified = "parameter not found"; // 7
const char *asynParamWrongTypeStringified = "wrong type"; // 8
const char *asynParamBadIndexStringified = "bad index"; // 9
const char *asynParamUndefinedStringified = "parameter undefined"; // 10
const char *asynParamInvalidListStringified = "invalid list"; // 11
const char asynSuccessStringified[] = "success"; // 0
const char asynTimeoutStringified[] = "timeout"; // 1
const char asynOverflowStringified[] = "overflow"; // 2
const char asynErrorStringified[] = "error"; // 3
const char asynDisconnectedStringified[] = "disconnected"; // 4
const char asynDisabledStringified[] = "disabled"; // 5
const char asynParamAlreadyExistsStringified[] =
"parameter already exists"; // 6
const char asynParamNotFoundStringified[] = "parameter not found"; // 7
const char asynParamWrongTypeStringified[] = "wrong type"; // 8
const char asynParamBadIndexStringified[] = "bad index"; // 9
const char asynParamUndefinedStringified[] = "parameter undefined"; // 10
const char asynParamInvalidListStringified[] = "invalid list"; // 11
const char inputDidNotMatchAsynStatus[] =
"Input did not match any variant of asynStatus";
const char *sinqController::stringifyAsynStatus(asynStatus status) {
// See
@ -88,6 +356,9 @@ const char *sinqController::stringifyAsynStatus(asynStatus status) {
// and
// https://github.com/epics-modules/asyn/blob/master/asyn/asynPortDriver/paramErrors.h
// for the definition of the error codes
// The pragma is necessary since the param lib error codes are "tacked onto"
// the enum, which results in compiler warnings otherwise.
#pragma GCC diagnostic ignored "-Wswitch"
switch (status) {
case asynSuccess:
return asynSuccessStringified;
@ -115,7 +386,162 @@ const char *sinqController::stringifyAsynStatus(asynStatus status) {
return asynParamInvalidListStringified;
}
errlogPrintf("%s => line %d:\nReached unreachable code.",
__PRETTY_FUNCTION__, __LINE__);
return "unreachable code reached";
}
asynPrint(
pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nInput did not match any variant of asynStatus.\n",
__PRETTY_FUNCTION__, __LINE__);
return inputDidNotMatchAsynStatus;
}
// =============================================================================
// Provide the setters to IOC shell
extern "C" {
/**
* @brief Enable / disable the watchdog (FFI implementation)
*
* @param portName Name of the controller
* @param axisNo Axis number
* @param enable If 0, disable the watchdog, otherwise enable
* it
* @return asynStatus
*/
asynStatus setWatchdogEnabled(const char *portName, int axisNo, int enable) {
sinqController *pC;
pC = (sinqController *)findAsynPortDriver(portName);
if (pC == nullptr) {
errlogPrintf("%s => line %d:\nPort %s not found.", __PRETTY_FUNCTION__,
__LINE__, portName);
return asynError;
}
asynMotorAxis *asynAxis = pC->getAxis(axisNo);
sinqAxis *axis = dynamic_cast<sinqAxis *>(asynAxis);
if (axis == nullptr) {
errlogPrintf("%s => line %d:\nAxis %d does not exist or is not an "
"instance of sinqAxis.",
__PRETTY_FUNCTION__, __LINE__, axisNo);
}
return axis->setWatchdogEnabled(enable != 0);
}
static const iocshArg setWatchdogEnabledArg0 = {"Controller port name",
iocshArgString};
static const iocshArg setWatchdogEnabledArg1 = {"Axis number", iocshArgInt};
static const iocshArg setWatchdogEnabledArg2 = {
"Enabling / disabling the watchdog", iocshArgInt};
static const iocshArg *const setWatchdogEnabledArgs[] = {
&setWatchdogEnabledArg0, &setWatchdogEnabledArg1, &setWatchdogEnabledArg2};
static const iocshFuncDef setWatchdogEnabledDef = {"setWatchdogEnabled", 3,
setWatchdogEnabledArgs};
static void setWatchdogEnabledCallFunc(const iocshArgBuf *args) {
setWatchdogEnabled(args[0].sval, args[1].ival, args[2].ival);
}
// =============================================================================
/**
* @brief Set the offsetMovTimeout (FFI implementation)
*
* @param portName Name of the controller
* @param axisNo Axis number
* @param offsetMovTimeout Offset (in seconds)
* @return asynStatus
*/
asynStatus setOffsetMovTimeout(const char *portName, int axisNo,
double offsetMovTimeout) {
sinqController *pC;
pC = (sinqController *)findAsynPortDriver(portName);
if (pC == nullptr) {
errlogPrintf("%s => line %d:\nPort %s not found.", __PRETTY_FUNCTION__,
__LINE__, portName);
return asynError;
}
asynMotorAxis *asynAxis = pC->getAxis(axisNo);
sinqAxis *axis = dynamic_cast<sinqAxis *>(asynAxis);
if (axis == nullptr) {
errlogPrintf("%s => line %d:\nAxis %d does not exist or is not an "
"instance of sinqAxis.",
__PRETTY_FUNCTION__, __LINE__, axisNo);
}
return axis->setOffsetMovTimeout(offsetMovTimeout);
}
static const iocshArg setOffsetMovTimeoutArg0 = {"Controller port name",
iocshArgString};
static const iocshArg setOffsetMovTimeoutArg1 = {"Axis number", iocshArgInt};
static const iocshArg setOffsetMovTimeoutArg2 = {"Offset timeout for movement",
iocshArgDouble};
static const iocshArg *const setOffsetMovTimeoutArgs[] = {
&setOffsetMovTimeoutArg0, &setOffsetMovTimeoutArg1,
&setOffsetMovTimeoutArg2};
static const iocshFuncDef setOffsetMovTimeoutDef = {"setOffsetMovTimeout", 3,
setOffsetMovTimeoutArgs};
static void setOffsetMovTimeoutCallFunc(const iocshArgBuf *args) {
setOffsetMovTimeout(args[0].sval, args[1].ival, args[2].dval);
}
// =============================================================================
/**
* @brief Set the setScaleMovTimeout (FFI implementation)
*
* @param portName Name of the controller
* @param axisNo Axis number
* @param scaleMovTimeout Scaling factor (in seconds)
* @return asynStatus
*/
asynStatus setScaleMovTimeout(const char *portName, int axisNo,
double scaleMovTimeout) {
sinqController *pC;
pC = (sinqController *)findAsynPortDriver(portName);
if (pC == nullptr) {
errlogPrintf("%s => line %d:\nPort %s not found.", __PRETTY_FUNCTION__,
__LINE__, portName);
return asynError;
}
asynMotorAxis *asynAxis = pC->getAxis(axisNo);
sinqAxis *axis = dynamic_cast<sinqAxis *>(asynAxis);
if (axis == nullptr) {
errlogPrintf("%s => line %d:\nAxis %d does not exist or is not an "
"instance of sinqAxis.",
__PRETTY_FUNCTION__, __LINE__, axisNo);
}
return axis->setScaleMovTimeout(scaleMovTimeout);
}
static const iocshArg setScaleMovTimeoutArg0 = {"Controller port name",
iocshArgString};
static const iocshArg setScaleMovTimeoutArg1 = {"Axis number", iocshArgInt};
static const iocshArg setScaleMovTimeoutArg2 = {
"Multiplier for calculated move time", iocshArgDouble};
static const iocshArg *const setScaleMovTimeoutArgs[] = {
&setScaleMovTimeoutArg0, &setScaleMovTimeoutArg1, &setScaleMovTimeoutArg2};
static const iocshFuncDef setScaleMovTimeoutDef = {"setScaleMovTimeout", 3,
setScaleMovTimeoutArgs};
static void setScaleMovTimeoutCallFunc(const iocshArgBuf *args) {
setScaleMovTimeout(args[0].sval, args[1].ival, args[2].dval);
}
// =============================================================================
static void sinqControllerRegister(void) {
iocshRegister(&setOffsetMovTimeoutDef, setOffsetMovTimeoutCallFunc);
iocshRegister(&setScaleMovTimeoutDef, setScaleMovTimeoutCallFunc);
iocshRegister(&setWatchdogEnabledDef, setWatchdogEnabledCallFunc);
}
epicsExportRegistrar(sinqControllerRegister);
} // extern C

View File

@ -1,10 +1,7 @@
/*
This class contains the necessary changes to have an additional text fields
for messages with each axis.
This class extends asynMotorController by some features used in SINQ.
Code lifted from Torsten Boegershausens ESS code.
Mark Koennecke, March 2017
Stefan Mathis, November 2024
*/
#ifndef __sinqController
@ -13,41 +10,149 @@
#define motorMessageIsFromDriverString "MOTOR_MESSAGE_DRIVER"
#define motorMessageTextString "MOTOR_MESSAGE_TEXT"
#define IncrementalEncoder "incremental"
#define AbsoluteEncoder "absolute"
#define NoEncoder "none"
class epicsShareClass sinqController : public asynMotorController {
public:
/**
* @brief Construct a new sinqController object
*
* @param portName Controller can be found by findAsynPortDriver
* with this name
* @param ipPortConfigName IP adress and port configuration of the
* controller unit, used to connect via pasynOctetSyncIO->connect
* @param numAxes Pointers to the axes are stored in the array
pAxes_ which has the length specified here. When getting an axis, the
`getAxis` function indexes into this array. A length of 8 would therefore
mean that the axis slots 0 to 7 are available. However, in order to keep the
axis enumeration in sync with the electronics counting logic, we start
counting the axes with 1 and end at 8. Therefore, an offset of 1 is added
when forwarding this number to asynMotorController.
* @param movingPollPeriod Time between polls when moving (in seconds)
* @param idlePollPeriod Time between polls when not moving (in
seconds)
* @param extraParams Number of extra parameter library entries
* created in a concrete driver implementation
*/
sinqController(const char *portName, const char *SINQPortName, int numAxes,
const int &extraParams = 2);
friend class sinqAxis;
double movingPollPeriod, double idlePollPeriod,
int numExtraParams);
/**
If accessing the parameter library failed (return status != asynSuccess),
calling this function writes a standardized message to both the IOC shell
and the motor message text PV. It then returns the input status.
* @brief Destroy the sinqController object
*
* In general, there is no real memory cleanup strategy in asynMotor,
* because objects are expected to be alive for the entire lifetime of the
* IOC. We just clean up the allocated axes array here.
*/
virtual ~sinqController(void);
/**
* @brief Overloaded function of asynMotorController
*
* The function is overloaded to allow enabling / disabling the motor.
*
* @param pasynUser Specify the axis via the asynUser
* @param value New value
* @return asynStatus
*/
virtual asynStatus writeInt32(asynUser *pasynUser, epicsInt32 value);
/**
* @brief Overloaded function of asynMotorController
*
* The function is overloaded to get readback values for the enabling /
* disabling status.
*
* @param pasynUser Specify the axis via the asynUser
* @param value Read-out value
* @return asynStatus
*/
asynStatus readInt32(asynUser *pasynUser, epicsInt32 *value);
/**
* @brief Error handling in case accessing the parameter library failed.
*
* If accessing the parameter library failed (return status !=
asynSuccess), calling this function writes a standardized message to both
the IOC shell and the motor message text PV. It then returns the input
status.
*
* @param status Status of the failed parameter library access
* @param parameter Name of the parameter, used to create the
error messages.
* @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 input status.
*/
asynStatus paramLibAccessFailed(asynStatus status, const char *parameter,
const char *functionName, int lineNumber);
/**
This function writes a standardized message to both the IOC shell and
* @brief Error handling in case parsing a command response failed.
*
* This function writes a standardized message to both the IOC shell and
the motor message text PV in case parsing a response (e.g. via sscanf)
failed. It always returns asynError.
failed. It always returns asynError. This is convenience feature so the
function call can be used as a return value in an error handling branch.
*
* @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 *response, int axisNo,
const char *functionName,
int lineNumber);
/**
Convert an asynStatus into a descriptive string. This string can then e.g.
be used to create debugging messages.
* @brief Convert an asynStatus into a descriptive string.
*
* @param status Status which should be converted to a string.
* @return const char*
*/
const char *stringifyAsynStatus(asynStatus status);
friend class sinqAxis;
protected:
asynUser *lowLevelPortUser_;
#define FIRST_SINQMOTOR_PARAM motorMessageText_
int motorMessageText_;
int motorTargetPosition_;
int motorEnable_;
int motorEnableRBV_;
int motorCanDisable_;
int motorEnableMovWatchdog_;
int motorCanSetSpeed_;
int motorLimitsOffset_;
/*
These parameters are here to write values from the hardware 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.
*/
int motorVeloFromDriver_;
int motorVbasFromDriver_;
int motorVmaxFromDriver_;
int motorAcclFromDriver_;
int motorHighLimitFromDriver_;
int motorLowLimitFromDriver_;
int encoderType_;
#define LAST_SINQMOTOR_PARAM encoderType_
};
#define NUM_SINQMOTOR_DRIVER_PARAMS \
(&LAST_SINQMOTOR_PARAM - &FIRST_SINQMOTOR_PARAM + 1)
#endif

4
src/sinqMotor.dbd Normal file
View File

@ -0,0 +1,4 @@
#---------------------------------------------
# SINQ specific DB definitions
#---------------------------------------------
registrar(sinqControllerRegister)