Reworked documentation after discussion with Electronics on 04.12.2024

This commit is contained in:
2024-12-05 10:49:01 +01:00
parent 86006e408a
commit be9f3b0d76
2 changed files with 115 additions and 48 deletions

161
README.md
View File

@ -4,73 +4,101 @@
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.
## Base classes
## 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-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.
### Architecture of EPICS motor drivers at SINQ
### 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.
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
### 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.
- `isEnabled`: This function returns whether the axis is currently enabled or not. 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`:
The full inheritance chain for two different motor drivers "a" and "b" looks for a like this:
`asynController -> sinqController -> aController`
`asynAxis -> sinqAxis -> aAxis`
Before calling `doPoll`:
- Try to execute `atFirstPoll` once during the lifetime of the IOC (and retry, if that failed)
`asynController -> sinqController -> bController`
`asynAxis -> sinqAxis -> bAxis`
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.
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.
## Database
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).
Besides the base classes, this EPICS module also offers an accompanying db/sinqMotor.db file. It can be parametrized with the `dbLoadTemplate` function from the IOC shell:
### 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:
```
require sinqMotor, x.x.x # This is the three-digit version number of the sinqMotor module
dbLoadTemplate "motor.substitutions"
#!/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()
```
with `motor.substitutions` looking like this:
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" then look like this:
```
file "$(sinqMotor_DB)/sinqMotor.db"
# 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:
```
file "$(SINQDBPATH)"
{
pattern
{ AXIS, M, DESC, EGU, DIR, MRES, MSGTEXTSIZE, ENABLEMOVWATCHDOG, LIMITSOFFSET, CANSETSPEED }
{ 1, "lin1", "lin1", mm, Pos, 0.001, 200, 1, 1.0, 1 }
{ 2, "rot1", "rot1", degree, Neg, 0.001, 200, 0, 1.0, 0 }
{ 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 `sinqMotor_DB` is generated automatically by `require` and corresponds to the module database path.
The variable `SINQDBPATH` has been set in "mcu1.cmd" before calling `dbLoadTemplate`.
### Mandatory parameters
#### Mandatory parameters
- `AXIS`: Index of the axis, corresponds to the physical connection of the axis to the MCU.
- `M`: Name of the motor as shown in EPICS.
- `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".
- `DESC`: Description of the motor. This field is just for documentation and is not needed for operating a motor.
- `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
#### Optional parameters
The default values for those parameters are given for the individual records in db/sinqMotor.db
- `MSGTEXTSIZE`: Buffer size for the motor message record in characters
- `ENABLEMOVWATCHDOG`: Sets `setWatchdogEnabled` during IOC startup to the given value.
@ -80,7 +108,7 @@ on the motor controller. For example, if this value is 1.0 and the read-out limi
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
### 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
@ -120,13 +148,52 @@ coupling, changes to the parameter library via setDoubleParam are NOT
transferred to (motor_record_pv_name).MRES or to
(motor_record_pv_name):Resolution.
## Versioning
## 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.
#### 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.
- `isEnabled`: This function returns whether the axis is currently enabled or not. 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 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
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
### 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` from the terminal.

View File

@ -37,7 +37,7 @@ record(ao,"$(INSTR)$(M):RecResolution") {
}
# This record contains messages from the driver (usually error messages).
# The macro $(MSGTEXTSIZE) can be used to set the maximum length of the message.
# 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") {