# SPDX-License-Identifier: GPL-3.0-only # 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". # - RTRY: The maximum number of times the motor record will try again to move to # the desired position. When the retry limit is reached, the motor record will # declare the motion finished. If the desired position was not reached, the # field MISS will be set to 1 and NICOS will emit a warning "Did not reach # target position". If this value is set to 0, the retry deadband is never # applied and therefore MISS will always be 0. The error message "Did not reach # target position" will therefore never appear. # - RDBD: Retry deadband: When the motor has finished a complete motion, # possibly including backlash takeout, the motor record will compare its current # position with the desired position. If the magnitude of the difference is # greater than RDBD, the motor will try again, as if the user had requested a # move from the now current position to the desired position. Only a limited # number of retries will be performed (see RTRY). If the given value is smaller # than MRES, it is set to MRES. In this version of the record, we set RDBD to a # very high value in order to suppress both retries and the NTM (new target # monitor) logic from issuing stop commands during overshoots (see # https://epics.anl.gov/bcda/synApps/motor/motorRecord.html#Fields_misc). 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(DHLM, "$(DHLM=0)") field(DLLM, "$(DLLM=0)") field(TWV,"1") field(RTRY,"0") field(RDBD, "$(RDBD=10e300)") # Suppress retries and overshoot stop commands field(BDST, "0") field(RMOD,"3") # Retry mode 3 ("In-Position"): This suppresses any retries from the motor record. } # This PV reads out the 10th bit of the MSTA field of the motor record, which # is the "motorStatusProblem_" bit. record(calc, "$(INSTR)$(M):StatusProblem") { field(INPA, "$(INSTR)$(M).MSTA CP") field(CALC, "A >> 9") } # If the value of this PV is 0, the according axis is currently disconnected from the controller. # Trying to give commands to a disconnected axis will result in an error message in the IOC shell # This record is coupled to the parameter library via motorConnected_ -> MOTOR_CONNECTED. record(longin, "$(INSTR)$(M):Connected") { field(DTYP, "asynInt32") field(INP, "@asyn($(CONTROLLER),$(AXIS)) MOTOR_CONNECTED") field(SCAN, "I/O Intr") field(PINI, "NO") field(VAL, "1") } # Call the reset function of the corresponding sinqAxis # This record is coupled to the parameter library via motorReset_ -> MOTOR_RESET. record(longout, "$(INSTR)$(M):Reset") { field(DTYP, "asynInt32") field(OUT, "@asyn($(CONTROLLER),$(AXIS),1) MOTOR_RESET") field(PINI, "NO") } # This PV allows force-stopping the motor record from within the driver by setting # the motorForceStop_ value in the parameter library to 1. It should be reset to 0 by the driver afterwards. # The implementation strategy is taken from https://epics.anl.gov/tech-talk/2022/msg00464.php. # This record is coupled to the parameter library via motorForceStop_ -> MOTOR_FORCE_STOP. record(longin, "$(INSTR)$(M):StopRBV") { field(DTYP, "asynInt32") field(INP, "@asyn($(CONTROLLER),$(AXIS)) MOTOR_FORCE_STOP") field(SCAN, "I/O Intr") field(FLNK, "$(INSTR)$(M):Stop2Field") } record(longout, "$(INSTR)$(M):Stop2Field") { field(DOL, "$(INSTR)$(M):StopRBV NPP") field(OUT, "$(INSTR)$(M).STOP") field(OMSL, "closed_loop") } # 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") 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)") } # If this PV has a value other than 0, adaptive polling for this axis is enabled. # The standard motor record behaviour is to poll all axis with the busy / move poll # period if at least one of the axes is moving. Adaptive polling changes this so # that only axes which were moving in the last poll are polled with the busy / move poll # period and all other axes are polled with the idle poll period. record(longout, "$(INSTR)$(M):AdaptivePolling") { field(DTYP, "asynInt32") field(OUT, "@asyn($(CONTROLLER),$(AXIS),1) ADAPTIVE_POLLING") field(PINI, "YES") field(VAL, "$(ADAPTPOLL=1)") } # The timeout mechanism for movements can be enabled / disabled by setting # this PV to 1 / 0. # This record is coupled to the parameter library via motorEnableMovWatchdog -> MOTOR_ENABLE_MOV_WATCHDOG. 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(VAL, "$(DHLM=0)") field(INP, "@asyn($(CONTROLLER),$(AXIS)) MOTOR_HIGH_LIMIT_FROM_DRIVER") field(SCAN, "I/O Intr") field(FLNK, "$(INSTR)$(M):PushDHLM2Field") field(PINI, "NO") } record(ao, "$(INSTR)$(M):PushDHLM2Field") { field(DOL, "$(INSTR)$(M):DHLM_RBV NPP") field(OUT, "$(INSTR)$(M).DHLM") field(OMSL, "closed_loop") field(PINI, "NO") } # 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(VAL, "$(DLLM=0)") field(INP, "@asyn($(CONTROLLER),$(AXIS)) MOTOR_LOW_LIMIT_FROM_DRIVER") field(SCAN, "I/O Intr") field(FLNK, "$(INSTR)$(M):PushDLLM2Field") field(PINI, "NO") } record(ao, "$(INSTR)$(M):PushDLLM2Field") { field(DOL, "$(INSTR)$(M):DLLM_RBV NPP") field(OUT, "$(INSTR)$(M).DLLM") field(OMSL, "closed_loop") field(PINI, "NO") } # 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 NPP") 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 NPP") 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 NPP") 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 NPP") 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") }