diff --git a/.github/workflows/ci-scripts-build-full.yml b/.github/workflows/ci-scripts-build-full.yml index a122758..e394837 100644 --- a/.github/workflows/ci-scripts-build-full.yml +++ b/.github/workflows/ci-scripts-build-full.yml @@ -175,7 +175,7 @@ jobs: name: "Win2019 MSC-19, static" steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: submodules: true - name: Automatic core dumper analysis diff --git a/.github/workflows/ci-scripts-build.yml b/.github/workflows/ci-scripts-build.yml index a7634f4..f45f1fb 100644 --- a/.github/workflows/ci-scripts-build.yml +++ b/.github/workflows/ci-scripts-build.yml @@ -73,7 +73,7 @@ jobs: name: "3.15 Win VS2019, static" steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: submodules: true - name: Automatic core dumper analysis diff --git a/.gitignore b/.gitignore index d1dd6b6..382ee17 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ # Local configuration files /configure/*.local +/iocs/*IOC*/configure/*.local # iocBoot generated files /iocBoot/*ioc*/cdCommands @@ -24,6 +25,7 @@ /iocs/*IOC*/iocBoot/*ioc*/dllPath.bat /iocs/*IOC*/iocBoot/*ioc*/envPaths /iocs/*IOC*/iocBoot/*ioc*/relPaths.sh +/iocs/*IOC*/iocBoot/*ioc*/.iocsh_history # Build directories O.*/ @@ -43,3 +45,10 @@ O.*/ auto_settings.sav* auto_positions.sav* phytronMotors.* +motorPhytron.cflags +motorPhytron.config +motorPhytron.creator +motorPhytron.creator.user +motorPhytron.cxxflags +motorPhytron.files +motorPhytron.includes diff --git a/README.md b/README.md index d3a5076..a618f87 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # motorPhytron -EPICS motor drivers for the following [Phytron GmbH](https://www.phytron.eu/) controllers: I1AM01 Stepper Motor Controller +EPICS motor drivers for the following [Phytron GmbH](https://www.phytron.eu/) controllers: phyMOTION I1AM01, I1AM02, I1EM01, I1EM02 or MCC-1, MCC-2 Stepper Motor Controller [![Build Status](https://github.com/epics-motor/motorPhytron/actions/workflows/ci-scripts-build.yml/badge.svg)](https://github.com/epics-motor/motorPhytron/actions/workflows/ci-scripts-build.yml) @@ -9,3 +9,7 @@ motorPhytron is a submodule of [motor](https://github.com/epics-modules/motor). motorPhytron can also be built outside of motor by copying it's ``EXAMPLE_RELEASE.local`` file to ``RELEASE.local`` and defining the paths to ``MOTOR`` and itself. motorPhytron contains an example IOC that is built if ``CONFIG_SITE.local`` sets ``BUILD_IOCS = YES``. The example IOC can be built outside of driver module. + +See more documentation in [phytronApp/src/README.txt](phytronApp/src/README.txt). + +SPDX-License-Identifier: EPICS diff --git a/iocs/phytronIOC/iocBoot/iocPhytron/motor.substitutions.phytron b/iocs/phytronIOC/iocBoot/iocPhytron/motor.substitutions.phytron index e41e780..e1e9bf6 100644 --- a/iocs/phytronIOC/iocBoot/iocPhytron/motor.substitutions.phytron +++ b/iocs/phytronIOC/iocBoot/iocPhytron/motor.substitutions.phytron @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: EPICS ################################################################################ # PORT - asynPort created for the Phytron controller # ADDR - Address of the axis - right digit represents the index of the axis @@ -5,13 +6,36 @@ # index of the I1AM01 module ################################################################################ +# phyMOTION (1st example using MRES, 2nd example using SREV/UREV) +#file "$(TOP)/db/Phytron_motor.db" +#{ +#pattern +#{P, N, M, PORT, ADDR, DESC, EGU, MRES, ERES, UEIP, DIR, VELO, VBAS, VMAX, ACCL, BDST, BVEL, BACC, PREC, RDBD, RTRY, PREC, OFF, DHLM, DLLM, OFF, MDEL, INIT} +#{TEST:, 1, "m$(N)", phyMotionPort, 11, "motor $(N)", mm, 0.000625, 0.00005, No, Pos, 2.5, 1, 25, .5, 0, 25, .2, 5, 0, 0, 5, 0, 1440, -1440, -0.49, 0.001, ""} +#{TEST:, 2, "m$(N)", phyMotionPort, 21, "motor $(N)", mm, 0.000625, 0.00005, No, Pos, 2.5, 1, 25, .5, 0, 25, .2, 5, 0, 0, 5, 0, 1440, -1440, -0.49, 0.001, ""} +#} +#file "$(TOP)/db/Phytron_motor.db" +#{ +#pattern +#{P, N, M, PORT, ADDR, DESC, EGU, SREV, UREV, ERES, UEIP, DIR, VELO, VBAS, VMAX, ACCL, BDST, BVEL, BACC, PREC, RDBD, RTRY, PREC, OFF, DHLM, DLLM, OFF, MDEL, INIT} +#{TEST:, 1, "m$(N)", phyMotionPort, 11, "motor $(N)", mm, 400, 0.25, 0.00005, No, Pos, 2.5, 1, 25, .5, 0, 25, .2, 5, 0, 0, 5, 0, 1440, -1440, -0.49, 0.001, ""} +#{TEST:, 2, "m$(N)", phyMotionPort, 21, "motor $(N)", mm, 400, 0.25, 0.00005, No, Pos, 2.5, 1, 25, .5, 0, 25, .2, 5, 0, 0, 5, 0, 1440, -1440, -0.49, 0.001, ""} +#} + +# MCC-2 (1st example using MRES, 2nd example using SREV/UREV) +#file "$(TOP)/db/Phytron_motor.db" +#{ +#pattern +#{P, N, M, PORT, ADDR, DESC, EGU, MRES, ERES, UEIP, DIR, VELO, VBAS, VMAX, ACCL, BDST, BVEL, BACC, PREC, RDBD, RTRY, DHLM, DLLM, OFF, MDEL, INIT} +#{TEST:, 1, "m$(N)", phyMotionPort, 11, "motor $(N)", Deg, 0.000625, 0.00005, No, Pos, 360, 180, 720, .5, 0, 180, .2, 5, 0, 0, 1440, -1440, -0.49, 0.001, ""} +#{TEST:, 2, "m$(N)", phyMotionPort, 12, "motor $(N)", Deg, 0.000625, 0.00005, No, Pos, 360, 180, 720, .5, 0, 180, .2, 5, 0, 0, 1440, -1440, -0.49, 0.001, ""} +#} file "$(TOP)/db/Phytron_motor.db" { pattern -{P, N, M, DTYP, PORT, ADDR, DESC, EGU, DIR, VELO, VBAS, VMAX, ACCL, BDST, BVEL, BACC, MRES, ERES, PREC, DHLM, DLLM, INIT} -{phytron:, 1, "m$(N)", "asynMotor", phyMotionPort, 11, "motor $(N)", Deg, Pos, 360, 180, 720, .5, 0, 180, .2, 1.8, 0.18, 5, 1440, -1440, ""} -{phytron:, 2, "m$(N)", "asynMotor", phyMotionPort, 21, "motor $(N)", Deg, Pos, 360, 180, 720, .5, 0, 180, .2, 1.8, 0.18, 5, 1440, -1440, ""} - +{P, N, M, PORT, ADDR, DESC, EGU, SREV, UREV, ERES, UEIP, DIR, VELO, VBAS, VMAX, ACCL, BDST, BVEL, BACC, PREC, RDBD, RTRY, DHLM, DLLM, OFF, MDEL, INIT} +{TEST:, 1, "m$(N)", phyMotionPort, 11, "motor $(N)", Deg, 400, 0.25, 0.00005, No, Pos, 360, 180, 720, .5, 0, 180, .2, 5, 0, 0, 1440, -1440, -0.49, 0.001, ""} +{TEST:, 2, "m$(N)", phyMotionPort, 12, "motor $(N)", Deg, 400, 0.25, 0.00005, No, Pos, 360, 180, 720, .5, 0, 180, .2, 5, 0, 0, 1440, -1440, -0.49, 0.001, ""} } ################################################################################ @@ -28,12 +52,19 @@ pattern # in the macros that follow INIT. If set to NO, records will not be # initialized - this option is useful if auto save/restore is used ################################################################################ -file "$(TOP)/db/Phytron_I1AM01.db" +#file "$(TOP)/db/Phytron_I1AM01.db" +#{ +#pattern +#{P, N, M, PORT, ADDR, SCAN, TIMEOUT, INIT, HOMING, MODE, POS_OFFSET, NEG_OFFSET, INIT_TIME, POS_TIME, BOOST, SWITCH_TYP, PWR_STAGE, ENC_TYP, ENC_SFI, ENC_DIR, STOP_CURR, RUN_CURR, BOOST_CURR, CURRENT_DELAY, STEP_RES, PS_MON, BRAKE_OUTPUT, DISABLE_MOTOR, BRAKE_ENGAGE_TIME, BRAKE_RELEASE_TIME } +#{TEST:, 1, "m$(N)", phyMotionPort, 11, "1 second", 1, YES, 0, 1, 0, 0, 20, 20, 0, 0, 1, 1, 0, 1 20, 80, 100, 20, 0, 1, 1.1, 1, 10, 10 } +#{TEST:, 2, "m$(N)", phyMotionPort, 21, "1 second", 1, YES, 0, 1, 0, 0, 20, 20, 0, 0, 1, 1, 0, 1 20, 80, 100, 20, 0, 1, 1.2, 1, 10, 10 } +#} +file "$(TOP)/db/Phytron_MCC.db" { pattern -{P, N, M, PORT, ADDR, SCAN, TIMEOUT, INIT, HOMING, MODE, POS_OFFSET, NEG_OFFSET, INIT_TIME, POS_TIME, BOOST, SWITCH_TYP, PWR_STAGE, ENC_TYP, ENC_SFI, ENC_DIR, STOP_CURR, RUN_CURR, BOOST_CURR, CURRENT_DELAY, STEP_RES, PS_MON } -{phytron, 1, ":m$(N)", phyMotionPort, 11, "1 second", 3, YES, 0, 1, 0, 0, 20, 20, 0, 0, 1, 1, 0, 1 20, 400, 500, 20, 0, 1} -{phytron, 2, ":m$(N)", phyMotionPort, 21, "1 second", 3, YES, 0, 1, 0, 0, 20, 20, 0, 0, 1, 1, 0, 1 20, 400, 500, 20, 0, 1} +{P, N, M, PORT, ADDR, SCAN, TIMEOUT, INIT, HOMING, MODE, POS_OFFSET, NEG_OFFSET, INIT_TIME, POS_TIME, BOOST, SWITCH_TYP, PWR_STAGE, ENC_TYP, ENC_SFI, ENC_DIR, STOP_CURR, RUN_CURR, BOOST_CURR, CURRENT_DELAY, STEP_RES, PS_MON, BRAKE_OUTPUT, DISABLE_MOTOR, BRAKE_ENGAGE_TIME, BRAKE_RELEASE_TIME } +{TEST:, 1, "m$(N)", phyMotionPort, 11, "5 second", 1, YES, 0, 1, 0, 0, 20, 20, 0, 0, 1, 0, 0, 1 20, 80, 100, 20, "1/16", 1, 1.1, 1, 10, 10 } +{TEST:, 2, "m$(N)", phyMotionPort, 12, "5 second", 1, YES, 0, 1, 0, 0, 20, 20, 0, 0, 1, 0, 0, 1 20, 80, 100, 20, "1/16", 1, 1.2, 1, 10, 10 } } ################################################################################ @@ -42,9 +73,37 @@ pattern # ADDR - Arbitrary value between 0 and 255 # TIMEOUT - asyn timeout ################################################################################ -file "$(TOP)/db/Phytron_MCM01.db" +#file "$(TOP)/db/Phytron_MCM01.db" +#{ +#pattern +#{P, DTYP, PORT, ADDR, TIMEOUT} +#{phytron, "asynMotor", phyMotionPort, 0, 10 } +#} + +################################################################################ +# P - Controller specific name +# PORT - asynPort created for the analog controller +################################################################################ +file "$(TOP)/db/Phytron_AIO_MCC.db" { -pattern -{P, DTYP, PORT, ADDR, TIMEOUT} -{phytron, "asynMotor", phyMotionPort, 0, 10 } +pattern{P,PORT} +{"phyA1", "phyA1"} +{"phyA2", "phyA2"} +#{"phyA3", "phyA3"} +#{"phyA4", "phyA4"} +} +file "$(TOP)/db/Phytron_DIO.db" +{ +pattern{P,PORT} +{"phyD1", "phyD1"} +} +#file "$(TOP)/db/Phytron_DIO_MCC1.db" +#{ +#pattern{P,PORT} +#{"phyD1", "phyD1"} +#} +file "$(TOP)/db/Phytron_CMD.db" +{ +pattern{P,PORT} +{"phy", "phyMotionPort"} } diff --git a/iocs/phytronIOC/iocBoot/iocPhytron/st.cmd b/iocs/phytronIOC/iocBoot/iocPhytron/st.cmd index 9b2e67d..c9dbb2b 100644 --- a/iocs/phytronIOC/iocBoot/iocPhytron/st.cmd +++ b/iocs/phytronIOC/iocBoot/iocPhytron/st.cmd @@ -1,4 +1,5 @@ #!../../bin/linux-x86_64/phytron +# SPDX-License-Identifier: EPICS < envPaths diff --git a/iocs/phytronIOC/iocBoot/iocPhytron/st.cmd.mcc b/iocs/phytronIOC/iocBoot/iocPhytron/st.cmd.mcc new file mode 100755 index 0000000..2c0114e --- /dev/null +++ b/iocs/phytronIOC/iocBoot/iocPhytron/st.cmd.mcc @@ -0,0 +1,68 @@ +#!../../bin/linux-x86_64/phytron +# SPDX-License-Identifier: EPICS + +< envPaths + +## Register all support components +dbLoadDatabase("../../dbd/phytron.dbd",0,0) +phytron_registerRecordDeviceDriver(pdbbase) + +#drvAsynIPPortConfigure("testPort","localhost:8000",0,0,1) +#drvAsynIPPortConfigure("testRemote","10.5.1.181:22222",0,0,1) +#drvAsynSerialPortConfigure("testRemote","/dev/ttyUSB0",0,0,1) +#asynSetOption("testRemote", -1, "baud", "115200") +#asynSetOption("testRemote", -1, "bits", "8") +#asynSetOption("testRemote", -1, "parity", "none") +#asynSetOption("testRemote", -1, "stop", "1") +#asynSetOption("testRemote", -1, "clocal", "N") +#asynSetOption("testRemote", -1, "crtscts", "N") +drvAsynIPPortConfigure("testRemote","192.168.167.116:22222",0,0,1) + +#eraseFile("device-comm.log") +#asynSetTraceFile("testRemote", 0, "device-comm.log") +#asynSetTraceMask("testRemote", 0, 0x2F) # error, device, filter, driver, warning, !flow +#asynSetTraceIOMask("testRemote", 0, 2) +#asynSetTraceInfoMask("testRemote", 0, 0x0F) + +#phytronCreateMCC(phytronPort, asynPort, address, movingPollPeriod, idlePollPeriod, timeout, do-not-reset +phytronCreateMCC("phyMotionPort", "testRemote", 0, 1000, 1000, 1000, 1) +asynSetOption("phyMotionPort", 0, "fakeHomedEnable", "true") + +#phytronCreateAxis(phytronPort, module, axis) +phytronCreateAxis("phyMotionPort", 1, 1) +phytronCreateAxis("phyMotionPort", 1, 2) + +dbLoadTemplate "motor.substitutions.phytron" + +# open listening server part +#drvAsynIPServerPortConfigure(asynPortServer,"localhost:port",maxClients,priority,disableAutoConnect,noProcessEos) +drvAsynIPServerPortConfigure("testRemote2","127.0.0.2:22222",10,0,0,0) +#drvAsynIPServerPortConfigure("testRemote2","localhost:22222",10,0,0,0) +#drvAsynIPServerPortConfigure("testRemote2",":22222",10,0,0,0) +#drvAsynIPServerPortConfigure("testRemote2","0.0.0.0:22222",10,0,0,0) +#drvAsynIPServerPortConfigure("testRemote2","192.168.167.254:22222",10,0,0,0) + +# create analog and digital I/O for MCC-2 +phytronCreateAnalog("phyA1", "phyMotionPort", 1, 1, "") +phytronCreateAnalog("phyA2", "phyMotionPort", 1, 2, "") +phytronCreateDigital("phyD1", "phyMotionPort", 1, 1, "") + +## configure AUTOSAVE +#epicsEnvSet("SAVE_DIR","$(TOP)/iocBoot/$(IOC)") +#epicsEnvSet("IOCNAME","$(IOC)-autosave") +#dbLoadRecords("$(AUTOSAVE)/asApp/Db/save_restoreStatus.db", "P=$(IOCNAME):") +#save_restoreSet_status_prefix("$(IOCNAME):") +#set_requestfile_path("$(SAVE_DIR)") +#set_savefile_path("$(SAVE_DIR)") +## Schedule a maximum of 3 sequenced backups of the .sav file +## every 10 minutes - .sav0, .sav1, .sav2 +#save_restoreSet_NumSeqFiles(3) +#save_restoreSet_SeqPeriodInSeconds(600) +#set_pass0_restoreFile("$(IOCNAME).sav") +#set_pass1_restoreFile("$(IOCNAME).sav") + +iocInit() + +## Create request file and start periodic 'save’ +#makeAutosaveFileFromDbInfo("$(SAVE_DIR)/$(IOCNAME).req", "autosaveFields") +#create_monitor_set("$(IOCNAME).req", 5) diff --git a/iocs/phytronIOC/iocBoot/iocPhytron/st.cmd.phytron b/iocs/phytronIOC/iocBoot/iocPhytron/st.cmd.phytron old mode 100644 new mode 100755 index 1113f74..17bcc47 --- a/iocs/phytronIOC/iocBoot/iocPhytron/st.cmd.phytron +++ b/iocs/phytronIOC/iocBoot/iocPhytron/st.cmd.phytron @@ -1,4 +1,5 @@ #!../../bin/linux-x86_64/phytron +# SPDX-License-Identifier: EPICS < envPaths @@ -8,14 +9,52 @@ phytron_registerRecordDeviceDriver(pdbbase) #drvAsynIPPortConfigure("testPort","localhost:8000",0,0,1) drvAsynIPPortConfigure("testRemote","10.5.1.181:22222",0,0,1) +#drvAsynSerialPortConfigure("testRemote","/dev/ttyUSB0",0,0,1) +#asynSetOption("testRemote", -1, "baud", "115200") +#asynSetOption("testRemote", -1, "bits", "8") +#asynSetOption("testRemote", -1, "parity", "none") +#asynSetOption("testRemote", -1, "stop", "1") +#asynSetOption("testRemote", -1, "clocal", "N") +#asynSetOption("testRemote", -1, "crtscts", "N") #phytronCreateController (phytronPort, asynPort, movingPollPeriod, idlePollPeriod, timeout) -phytronCreateController ("phyMotionPort", "testRemote", 100, 100, 1000) +phytronCreateController ("phyMotionPort", "testRemote", 10, 10, 1000) +asynSetOption("phyMotionPort", 0, "pollMethod", "parallel") +asynSetOption("phyMotionPort", 0, "fakeHomedEnable", "true") +asynSetOption("phyMotionPort", 0, "clearAxisStatus", "true") #phytronCreateAxis(phytronPort, module, axis) phytronCreateAxis("phyMotionPort", 1, 1) phytronCreateAxis("phyMotionPort", 2, 1) +phytronCreateAxis("phyMotionPort", 3, 1) + +# create analog I/O with a AIOM01.1 +phytronCreateAnalog("phyA1", "phyMotionPort", 1, 1, "") +phytronCreateAnalog("phyA2", "phyMotionPort", 1, 2, "") +phytronCreateAnalog("phyA3", "phyMotionPort", 1, 3, "") +phytronCreateAnalog("phyA4", "phyMotionPort", 1, 4, "") + +# create digital I/O with a DIOM01.1 +phytronCreateDigital("phyD1", "phyMotionPort", 1, 1, "") dbLoadTemplate "motor.substitutions.phytron" +## configure AUTOSAVE +#epicsEnvSet("SAVE_DIR","$(TOP)/iocBoot/$(IOC)") +#epicsEnvSet("IOCNAME","$(IOC)-autosave") +#dbLoadRecords("$(AUTOSAVE)/asApp/Db/save_restoreStatus.db", "P=$(IOCNAME):") +#save_restoreSet_status_prefix("$(IOCNAME):") +#set_requestfile_path("$(SAVE_DIR)") +#set_savefile_path("$(SAVE_DIR)") +## Schedule a maximum of 3 sequenced backups of the .sav file +## every 10 minutes - .sav0, .sav1, .sav2 +#save_restoreSet_NumSeqFiles(3) +#save_restoreSet_SeqPeriodInSeconds(600) +#set_pass0_restoreFile("$(IOCNAME).sav") +#set_pass1_restoreFile("$(IOCNAME).sav") + iocInit() + +## Create request file and start periodic 'save’ +#makeAutosaveFileFromDbInfo("$(SAVE_DIR)/$(IOCNAME).req", "autosaveFields") +#create_monitor_set("$(IOCNAME).req", 5) diff --git a/iocs/phytronIOC/phytronApp/Db/Makefile b/iocs/phytronIOC/phytronApp/Db/Makefile index 98e5551..37aaad2 100644 --- a/iocs/phytronIOC/phytronApp/Db/Makefile +++ b/iocs/phytronIOC/phytronApp/Db/Makefile @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: EPICS TOP=../.. include $(TOP)/configure/CONFIG #---------------------------------------- @@ -8,11 +9,23 @@ include $(TOP)/configure/CONFIG #DB_OPT = YES ifdef MOTOR_PHYTRON +DB_INSTALLS += $(MOTOR_PHYTRON)/db/Phytron_AIO.db +DB_INSTALLS += $(MOTOR_PHYTRON)/db/Phytron_AIO_MCC.db +DB_INSTALLS += $(MOTOR_PHYTRON)/db/Phytron_CMD.db +DB_INSTALLS += $(MOTOR_PHYTRON)/db/Phytron_DIO.db +DB_INSTALLS += $(MOTOR_PHYTRON)/db/Phytron_DIO_MCC1.db DB_INSTALLS += $(MOTOR_PHYTRON)/db/Phytron_I1AM01.db +DB_INSTALLS += $(MOTOR_PHYTRON)/db/Phytron_MCC.db DB_INSTALLS += $(MOTOR_PHYTRON)/db/Phytron_MCM01.db DB_INSTALLS += $(MOTOR_PHYTRON)/db/Phytron_motor.db else +DB_INSTALLS += $(MOTOR)/db/Phytron_AIO.db +DB_INSTALLS += $(MOTOR)/db/Phytron_AIO_MCC.db +DB_INSTALLS += $(MOTOR)/db/Phytron_CMD.db +DB_INSTALLS += $(MOTOR)/db/Phytron_DIO.db +DB_INSTALLS += $(MOTOR)/db/Phytron_DIO_MCC1.db DB_INSTALLS += $(MOTOR)/db/Phytron_I1AM01.db +DB_INSTALLS += $(MOTOR)/db/Phytron_MCC.db DB_INSTALLS += $(MOTOR)/db/Phytron_MCM01.db DB_INSTALLS += $(MOTOR)/db/Phytron_motor.db endif diff --git a/iocs/phytronIOC/phytronApp/src/Makefile b/iocs/phytronIOC/phytronApp/src/Makefile index 8c6524b..d3d1634 100644 --- a/iocs/phytronIOC/phytronApp/src/Makefile +++ b/iocs/phytronIOC/phytronApp/src/Makefile @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: EPICS TOP=../.. include $(TOP)/configure/CONFIG @@ -22,6 +23,10 @@ phytron_DBD += base.dbd #ifdef ASYN phytron_DBD += asyn.dbd phytron_DBD += drvAsynIPPort.dbd +phytron_DBD += drvAsynSerialPort.dbd +#endif +#ifdef AUTOSAVE +phytron_DBD += asSupport.dbd #endif phytron_DBD += motorSupport.dbd phytron_DBD += phytronSupport.dbd @@ -32,6 +37,9 @@ phytron_LIBS += motor #ifdef ASYN phytron_LIBS += asyn #endif +#ifdef AUTOSAVE +phytron_LIBS += autosave +#endif #ifdef SNCSEQ phytron_LIBS += seq pv #endif diff --git a/iocs/phytronIOC/phytronApp/src/phytronMain.cpp b/iocs/phytronIOC/phytronApp/src/phytronMain.cpp index 9f1be28..8eae9b9 100644 --- a/iocs/phytronIOC/phytronApp/src/phytronMain.cpp +++ b/iocs/phytronIOC/phytronApp/src/phytronMain.cpp @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: EPICS */ /* phytronMain.cpp */ /* Author: Marty Kraimer Date: 17MAR2000 */ diff --git a/phytronApp/Db/Makefile b/phytronApp/Db/Makefile index 6807d38..285e936 100644 --- a/phytronApp/Db/Makefile +++ b/phytronApp/Db/Makefile @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: EPICS TOP=../.. include $(TOP)/configure/CONFIG #---------------------------------------- @@ -11,7 +12,13 @@ include $(TOP)/configure/CONFIG # Create and install (or just install) into /db # databases, templates, substitutions like this #DB += xxx.db +DB += Phytron_AIO.db +DB += Phytron_AIO_MCC.db +DB += Phytron_CMD.db +DB += Phytron_DIO.db +DB += Phytron_DIO_MCC1.db DB += Phytron_I1AM01.db +DB += Phytron_MCC.db DB += Phytron_MCM01.db DB += Phytron_motor.db diff --git a/phytronApp/Db/Phytron_AIO.db b/phytronApp/Db/Phytron_AIO.db new file mode 100644 index 0000000..3f73482 --- /dev/null +++ b/phytronApp/Db/Phytron_AIO.db @@ -0,0 +1,58 @@ +# SPDX-License-Identifier: EPICS +################################################################################ +# Phytron phyMOTION example for AIOM01.1 module with +# 4 analog inputs (14bit resolution) +# 4 analog outputs (16bit resolution) +# or one pair of AIM01.1 plus AOM01.1 module +# the analog parts could be bipolar -10V..+10V or 0..10V or 0..20mA + +################################################################################ +# outputs (AOM01.1 or AIOM01.1) +record(ao, "$(P):setVolt$(PORT)") +{ + field(DESC,"set $(PORT)") + field(DTYP,"asynInt32") + field(OUT, "@asyn($(PORT),0)OUT") + field(LINR,"SLOPE") + field(ESLO,"0.00030517878125") + field(EOFF,"0") + field(EGU ,"V") + field(LOPR,"-10") + field(HOPR,"10") + field(PREC,"4") + field(FLNK,"$(P):rbkVolt$(PORT)") +} +# read back the output value +record(ai,"$(P):rbkVolt$(PORT)") +{ + field(DESC,"hw-set $(PORT)") + field(DTYP,"asynInt32") + field(INP, "@asyn($(PORT),0)OUT") + field(SCAN,"$(SCAN=I/O Intr)") + field(LINR,"SLOPE") + field(ESLO,"0.001220703125") + field(EOFF,"0") + field(EGU ,"V") + field(LOPR,"-10") + field(HOPR,"10") + field(PREC,"4") + field(FLNK,"$(P):rdVolt$(PORT)") +} + +################################################################################ +# inputs (AIM01.1 or AIOM01.1) +record(ai,"$(P):rdVolt$(PORT)") +{ + field(DESC,"read $(PORT)") + field(DTYP,"asynInt32") + field(INP, "@asyn($(PORT),0)IN") + field(SCAN,"$(SCAN=I/O Intr)") + field(LINR,"SLOPE") + field(ESLO,"0.001220703125") + field(EOFF,"0") + field(EGU ,"V") + field(LOPR,"-10") + field(HOPR,"10") + field(PREC,"4") + field(FLNK,"$(FLNK=)") +} diff --git a/phytronApp/Db/Phytron_AIO_MCC.db b/phytronApp/Db/Phytron_AIO_MCC.db new file mode 100644 index 0000000..9850b94 --- /dev/null +++ b/phytronApp/Db/Phytron_AIO_MCC.db @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: EPICS +################################################################################ +# Phytron MCC-1 (MCC-2) example +# 1 (2) analog inputs (0V..5V, 10bit resolution) + +################################################################################ +# inputs +record(ai,"$(P):rdVolt$(PORT)") +{ + field(DESC,"read $(PORT)") + field(DTYP,"asynInt32") + field(INP,"@asyn($(PORT),0)IN") + field(SCAN,"$(SCAN=I/O Intr)") + field(LINR,"SLOPE") + field(ESLO,"0.0048828125") + field(EOFF,"0") + field(EGU ,"V") + field(LOPR,"0") + field(HOPR,"5") + field(PREC,"4") + field(FLNK,"$(FLNK=)") +} diff --git a/phytronApp/Db/Phytron_CMD.db b/phytronApp/Db/Phytron_CMD.db new file mode 100644 index 0000000..3e9a94a --- /dev/null +++ b/phytronApp/Db/Phytron_CMD.db @@ -0,0 +1,42 @@ +# SPDX-License-Identifier: EPICS +################################################################################ +# Phytron direct command communication + +record(stringout, "$(P):directcommand") +{ + field(DESC,"command to send") + field(DTYP,"asynOctetWrite") + field(OUT,"@asyn($(PORT),0)DIRECT_COMMAND") + field(FLNK,"$(P):directreply") +} + +record(stringin,"$(P):directreply") +{ + field(DESC,"received reply") + field(DTYP,"asynOctetRead") + field(INP,"@asyn($(PORT),0)DIRECT_REPLY") + field(SCAN,"$(SCAN=I/O Intr)") + field(FLNK,"$(P):directstatus") +} +record(mbbi,"$(P):directstatus") +{ + field(DESC,"command status") + field(DTYP,"asynInt32") + field(INP,"@asyn($(PORT),0)DIRECT_STATUS") + field(ZRST,"UNKNOWN") + field(ZRVL,"0") + field(ONST,"ACK") + field(ONVL,"1") + field(TWST,"NAK") + field(TWVL,"2") + field(THST,"ERROR") + field(THVL,"3") + field(SCAN,"$(SCAN=I/O Intr)") + field(FLNK,"$(P):directreplyInt") +} +record(longin,"$(P):directreplyInt") +{ + field(DESC,"reply as integer") + field(INP,"$(P):directreply") + field(FLNK,"$(FLNK=)") +} diff --git a/phytronApp/Db/Phytron_DIO.db b/phytronApp/Db/Phytron_DIO.db new file mode 100644 index 0000000..5fd3596 --- /dev/null +++ b/phytronApp/Db/Phytron_DIO.db @@ -0,0 +1,39 @@ +# SPDX-License-Identifier: EPICS +################################################################################ +# Phytron phyMOTION example for DIOM01.1 module with +# 8 digital inputs +# 8 digital outputs +# or a Phytron MCC-1 (MCC-2) controller with +# 8 digital inputs (MCC-1 configurable) +# 8 digital outputs (MCC-1 configurable) +# with 24V logic + +################################################################################ +# outputs +record(longout, "$(P):setDO$(PORT)") +{ + field(DESC,"set $(PORT)") + field(DTYP,"asynInt32") + field(OUT,"@asyn($(PORT),0)OUT") + field(FLNK,"$(P):rbkDO$(PORT)") +} +# read back the output value +record(longin,"$(P):rbkDO$(PORT)") +{ + field(DESC,"hw-set $(PORT)") + field(DTYP,"asynInt32") + field(INP,"@asyn($(PORT),0)OUT") + field(SCAN,"$(SCAN=I/O Intr)") + field(FLNK,"$(P):rdDI$(PORT)") +} + +################################################################################ +# inputs +record(longin,"$(P):rdDI$(PORT)") +{ + field(DESC,"read $(PORT)") + field(DTYP,"asynInt32") + field(INP,"@asyn($(PORT),0)IN") + field(SCAN,"$(SCAN=I/O Intr)") + field(FLNK,"$(FLNK=)") +} diff --git a/phytronApp/Db/Phytron_DIO_MCC1.db b/phytronApp/Db/Phytron_DIO_MCC1.db new file mode 100644 index 0000000..8776609 --- /dev/null +++ b/phytronApp/Db/Phytron_DIO_MCC1.db @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: EPICS +################################################################################ +# Phytron MCC-1 controller digital I/O direction +# MCC-1 has 8 bidirectional I/O pins, which have to be configured: +# - a cleared bit is an output +# - a set bit is an input + +record(mbboDirect, "$(P):dir$(PORT)") +{ + field(DESC,"direction bits (0=O,1=I) $(PORT)") + field(DTYP,"asynInt32") + field(OUT,"@asyn($(PORT),0)DIR") + field(FLNK,"$(FLNK=)") +} diff --git a/phytronApp/Db/Phytron_I1AM01.db b/phytronApp/Db/Phytron_I1AM01.db index 3b83fb4..fb29f87 100644 --- a/phytronApp/Db/Phytron_I1AM01.db +++ b/phytronApp/Db/Phytron_I1AM01.db @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: EPICS ################################################################################ # This database contains records corresponding to additional parameters (not # handled by the motor record) of the I1AM01 motion controller. diff --git a/phytronApp/Db/Phytron_MCC.db b/phytronApp/Db/Phytron_MCC.db new file mode 100644 index 0000000..647eade --- /dev/null +++ b/phytronApp/Db/Phytron_MCC.db @@ -0,0 +1,1002 @@ +# SPDX-License-Identifier: EPICS +################################################################################ +# This database contains records corresponding to additional axis parameters +# (not handled by the motor record) of the MCC-1/MCC-2 motion controller. +# +# Output records, used to set the parameter values have a suffix _SET, readback +# records, used to read the parameters have a suffix _GET. +# +# Initialization, status and reset records have no suffixes. +################################################################################ + +################################################################################ +# Reset Axis +################################################################################ +record(bo, "$(P)$(M)-RESET") +{ + field(DESC, "Reset axis") + field(DTYP, "asynInt32") + field(OUT, "@asyn($(PORT), $(ADDR), $(TIMEOUT))AXIS_RESET") +} + +################################################################################ +# Reset Axis Status +################################################################################ +record(bo, "$(P)$(M)-RESET-STATUS") +{ + field(DESC, "Reset axis status") + field(DTYP, "asynInt32") + field(OUT, "@asyn($(PORT), $(ADDR), $(TIMEOUT))AXIS_STATUS_RESET") +} + + +################################################################################ +# Initialization of _SET records +# REINIT_ is triggered every time the controller is reset +################################################################################ +record(seq, "$(P)$(M)-INIT-AXIS-1") +{ + field(DESC, "Axis initialization") + field(LNK1, "$(P)$(M)-HOMING_SET PP") + field(DOL1, "$(HOMING)") + field(LNK2, "$(P)$(M)-MOVE-TYP_SET PP") + field(DOL2, "$(MODE)") + field(LNK3, "$(P)$(M)-POS-OFFSET_SET PP") + field(DOL3, "$(POS_OFFSET)") + field(LNK4, "$(P)$(M)-NEG-OFFSET_SET PP") + field(DOL4, "$(NEG_OFFSET)") + field(LNK5, "$(P)$(M)-INIT-TIMEOUT_SET PP") + field(DOL5, "$(INIT_TIME)") + field(LNK6, "$(P)$(M)-POS-TIMEOUT_SET PP") + field(DOL6, "$(POS_TIME)") + field(LNK7, "$(P)$(M)-BOOST_SET PP") + field(DOL7, "$(BOOST)") + field(LNK8, "$(P)$(M)-SWITCH-TYP_SET PP") + field(DOL8, "$(SWITCH_TYP)") + field(LNK9, "$(P)$(M)-PWR-STAGE-MODE_SET PP") + field(DOL9, "$(PWR_STAGE)") + field(SELM, "All") + field(PINI, "$(INIT)") + field(FLNK, "$(P)$(M)-INIT-AXIS-2") +} + +record(seq, "$(P)$(M)-INIT-AXIS-2") +{ + field(DESC, "Axis initialization") + field(LNK1, "$(P)$(M)-ENC-TYP_SET PP") + field(DOL1, "$(ENC_TYP)") + field(LNK2, "$(P)$(M)-ENC-SFI_SET PP") + field(DOL2, "$(ENC_SFI)") + field(LNK3, "$(P)$(M)-STOP-CURRENT_SET PP") + field(DOL3, "$(STOP_CURR)") + field(LNK4, "$(P)$(M)-RUN-CURRENT_SET PP") + field(DOL4, "$(RUN_CURR)") + field(LNK5, "$(P)$(M)-BOOST-CURRENT_SET PP") + field(DOL5, "$(BOOST_CURR)") + field(LNK6, "$(P)$(M)-CURRENT-DELAY_SET PP") + field(DOL6, "$(CURRENT_DELAY)") + field(LNK7, "$(P)$(M)-STEP-RES_SET PP") + field(DOL7, "$(STEP_RES)") + field(LNK8, "$(P)$(M)-PS-MONITOR_SET PP") + field(DOL8, "$(PS_MON)") + field(LNK9, "$(P)$(M)-ENC-DIR_SET PP") + field(DOL9, "$(ENC_DIR)") + field(SELM, "All") +} + +record(seq, "$(P)$(M)-REINIT_") +{ + field(DESC, "Reinit after reset") + field(SCAN, "Passive") + field(DOL1, "$(P)-RESET_ CPP") + field(LNK1, "$(P)$(M)-INIT-AXIS-1.PROC") + field(SELM, "All") +} + +################################################################################ +# STATUS_ record reads axis status from the device, the two mbbiDirect records +# are used to parse the status bits. STATUS_ uses I/O Intr SCAN, because the +# status value is read in phytronAxis::poll +# SHFT field is used, therefore DTYP of mbbiDirect must be set to Raw Soft Channel. +################################################################################ +record(ai, "$(P)$(M)-STATUS_") +{ + field(DESC, "Axis status") + field(DTYP, "asynInt32") + field(INP, "@asyn($(PORT), $(ADDR), $(TIMEOUT))AXIS_STATUS") + field(SCAN, "I/O Intr") + field(FLNK, "$(P)$(M)-STATUS-1") +} + +record(mbbiDirect, "$(P)$(M)-STATUS-1") +{ + field(DESC, "Axis status bits") + field(DTYP, "Raw Soft Channel") + field(INP, "$(P)$(M)-STATUS_") + field(FLNK, "$(P)$(M)-STATUS-2") +} + +record(mbbiDirect, "$(P)$(M)-STATUS-2") +{ + field(DESC, "Axis status bits") + field(DTYP, "Raw Soft Channel") + field(INP, "$(P)$(M)-STATUS_") + field(SHFT, "16") + field(FLNK, "$(P)$(M)-AXIS-BUSY") +} + +################################################################################ +# Axis status records. See DESC field +################################################################################ +record(bi, "$(P)$(M)-AXIS-BUSY") +{ + field(DESC, "Axis busy") + field(INP, "$(P)$(M)-STATUS-1.B0") + field(ZNAM, "NOT BUSY") + field(ONAM, "BUSY") + field(FLNK, "$(P)$(M)-COMMAND") +} + +record(bi, "$(P)$(M)-COMMAND") +{ + field(DESC, "Command invalid") + field(INP, "$(P)$(M)-STATUS-1.B1") + field(ZNAM, "VALID") + field(ONAM, "INVALID") + field(FLNK, "$(P)$(M)-WAIT-SYNC") +} + +record(bi, "$(P)$(M)-WAIT-SYNC") +{ + field(DESC, "Waiting for synchronization") + field(INP, "$(P)$(M)-STATUS-1.B2") + field(ZNAM, "NO") + field(ONAM, "YES") + field(FLNK, "$(P)$(M)-INITIALIZED") +} + +record(bi, "$(P)$(M)-INITIALIZED") +{ + field(DESC, "Axis initialized") + field(INP, "$(P)$(M)-STATUS-1.B3") + field(ZNAM, "NO") + field(ONAM, "YES") + field(FLNK, "$(P)$(M)-LS-POS") +} + +record(bi, "$(P)$(M)-LS-POS") +{ + field(DESC, "Positive limit switch") + field(INP, "$(P)$(M)-STATUS-1.B4") + field(ZNAM, "NOT ACTIVE") + field(ONAM, "ACTIVE") + field(FLNK, "$(P)$(M)-LS-NEG") +} + +record(bi, "$(P)$(M)-LS-NEG") +{ + field(DESC, "Negative limit switch") + field(INP, "$(P)$(M)-STATUS-1.B5") + field(ZNAM, "NOT ACTIVE") + field(ONAM, "ACTIVE") + field(FLNK, "$(P)$(M)-LS-CNT") +} + +record(bi, "$(P)$(M)-LS-CNT") +{ + field(DESC, "Center switch") + field(INP, "$(P)$(M)-STATUS-1.B6") + field(ZNAM, "NOT ACTIVE") + field(ONAM, "ACTIVE") + field(FLNK, "$(P)$(M)-SWL-POS") +} + +record(bi, "$(P)$(M)-SWL-POS") +{ + field(DESC, "Positive software limit") + field(INP, "$(P)$(M)-STATUS-1.B7") + field(ZNAM, "NOT ACTIVE") + field(ONAM, "ACTIVE") + field(FLNK, "$(P)$(M)-SWL-NEG") +} + +record(bi, "$(P)$(M)-SWL-NEG") +{ + field(DESC, "Negative software limit") + field(INP, "$(P)$(M)-STATUS-1.B8") + field(ZNAM, "NOT ACTIVE") + field(ONAM, "ACTIVE") + field(FLNK, "$(P)$(M)-INT-ERR") +} + +record(bi, "$(P)$(M)-INT-ERR") +{ + field(DESC, "Axis internal error") + field(INP, "$(P)$(M)-STATUS-1.BB") + field(ZNAM, "NO") + field(ONAM, "YES") + field(FLNK, "$(P)$(M)-LS-ERR") +} + +record(bi, "$(P)$(M)-LS-ERR") +{ + field(DESC, "Axis Limit switch error") + field(INP, "$(P)$(M)-STATUS-1.BC") + field(ZNAM, "NO") + field(ONAM, "YES") + field(FLNK, "$(P)$(M)-PS-ERR") +} + +record(bi, "$(P)$(M)-PS-ERR") +{ + field(DESC, "Power stage error") + field(INP, "$(P)$(M)-STATUS-1.BD") + field(ZNAM, "NO") + field(ONAM, "YES") + field(FLNK, "$(P)$(M)-SFI-ERR") +} + +record(bi, "$(P)$(M)-SFI-ERR") +{ + field(DESC, "Axis SFI error") + field(INP, "$(P)$(M)-STATUS-1.BE") + field(ZNAM, "NO") + field(ONAM, "YES") + field(FLNK, "$(P)$(M)-ENDAT-ERR") +} + +record(bi, "$(P)$(M)-ENDAT-ERR") +{ + field(DESC, "Axis ENDAT error") + field(INP, "$(P)$(M)-STATUS-1.BF") + field(ZNAM, "NO") + field(ONAM, "YES") + field(FLNK, "$(P)$(M)-RUNNING") +} + +record(bi, "$(P)$(M)-RUNNING") +{ + field(DESC, "Axis is running") + field(INP, "$(P)$(M)-STATUS-2.B0") + field(ZNAM, "NO") + field(ONAM, "YES") + field(FLNK, "$(P)$(M)-RECOVERY") +} + +record(bi, "$(P)$(M)-RECOVERY") +{ + field(DESC, "Axis is in recovery") + field(INP, "$(P)$(M)-STATUS-2.B1") + field(ZNAM, "NO") + field(ONAM, "YES") + field(FLNK, "$(P)$(M)-STP-DLY") +} + +record(bi, "$(P)$(M)-STP-DLY") +{ + field(DESC, "Axis in stop current delay") + field(INP, "$(P)$(M)-STATUS-2.B2") + field(ZNAM, "NO") + field(ONAM, "YES") + field(FLNK, "$(P)$(M)-POSITIONED") +} + +record(bi, "$(P)$(M)-POSITIONED") +{ + field(DESC, "Axis in position") + field(INP, "$(P)$(M)-STATUS-2.B3") + field(ZNAM, "NO") + field(ONAM, "YES") + field(FLNK, "$(P)$(M)-APS-READY") +} + +record(bi, "$(P)$(M)-APS-READY") +{ + field(DESC, "Axis aps ready") + field(INP, "$(P)$(M)-STATUS-2.B4") + field(ZNAM, "NO") + field(ONAM, "YES") + field(FLNK, "$(P)$(M)-POSITIONING") +} + +record(bi, "$(P)$(M)-POSITIONING") +{ + field(DESC, "Axis in positioning mode") + field(INP, "$(P)$(M)-STATUS-2.B5") + field(ZNAM, "NO") + field(ONAM, "YES") + field(FLNK, "$(P)$(M)-FREE-RUN") +} + +record(bi, "$(P)$(M)-FREE-RUN") +{ + field(DESC, "Axis in free running") + field(INP, "$(P)$(M)-STATUS-2.B6") + field(ZNAM, "NO") + field(ONAM, "YES") + field(FLNK, "$(P)$(M)-MULTI-F") +} + +record(bi, "$(P)$(M)-MULTI-F") +{ + field(DESC, "Axis multi F run") + field(INP, "$(P)$(M)-STATUS-2.B7") + field(ZNAM, "NO") + field(ONAM, "YES") + field(FLNK, "$(P)$(M)-SYNC-ALLOWED") +} + +record(bi, "$(P)$(M)-SYNC-ALLOWED") +{ + field(DESC, "Axis multi F run") + field(INP, "$(P)$(M)-STATUS-2.B8") + field(ZNAM, "NO") + field(ONAM, "YES") + field(FLNK, "$(P)$(M)-AXIS-ERR") +} + +record(calc, "$(P)$(M)-AXIS-ERR") +{ + field(DESC, "Any axis err") + field(INPA, "$(P)$(M)-COMMAND") + field(INPB, "$(P)$(M)-LS-POS") + field(INPC, "$(P)$(M)-LS-NEG") + field(INPD, "$(P)$(M)-LS-CNT") + field(INPE, "$(P)$(M)-SWL-POS") + field(INPF, "$(P)$(M)-SWL-NEG") + field(INPG, "$(P)$(M)-INT-ERR") + field(INPH, "$(P)$(M)-LS-ERR") + field(INPI, "$(P)$(M)-PS-ERR") + field(INPJ, "$(P)$(M)-SFI-ERR") + field(INPK, "$(P)$(M)-ENDAT-ERR") + field(CALC, "A||B||C||D||E||F||G||H||I||J||K") + +} + +################################################################################ +# Homing procedure type +# KEEP IN MIND PARAMETERS THAT HOMING FINISHES BY MOVING FOR AND ADDITIONAL +# OFFSET, DEFINED IN PARAMETERS P11 AND P12 +################################################################################ +record(mbbo, "$(P)$(M)-HOMING_SET") +{ + field(DESC, "Homing Type") + field(DTYP, "asynInt32") + field(OUT, "@asyn($(PORT), $(ADDR), $(TIMEOUT))HOMING_PROCEDURE") + field(ZRST, "Limit") #HOMF (R+), HOMR (R-) + field(ZRVL, "0") + field(ONST, "Center") #HOMF (R+C)-Offset P11, HOMR (R-C)+Offset P12 + field(ONVL, "1") + field(TWST, "Encoder") #HOMF (R+I), HOMR (R+I), Offsets + field(TWVL, "2") + field(THST, "Limit-Encoder") #HOMF (R+^I), HOMR (R-^I), Offsets + field(THVL, "3") + field(FRST, "Center-Encoder") #HOMF (R+C^I), HOMR (R-C^I), Offsets + field(FRVL, "4") + field(FVST, "Reference-Center") #HOMF RC+, HOMR RC-, Offsets + field(FVVL, "5") + field(SXST, "Ref-Center-Encoder") #HOMF RC+^I, HOMR RC-^I, Offsets + field(SXVL, "6") + field(FLNK, "$(P)$(M)-HOMING_GET") + info(autosaveFields, "VAL") +} + +record(mbbi, "$(P)$(M)-HOMING_GET") +{ + field(DESC, "Homing Type") + field(DTYP, "asynInt32") + field(INP, "@asyn($(PORT), $(ADDR), $(TIMEOUT))HOMING_PROCEDURE") + field(ZRST, "Limit") #HOMF (R+), HOMR (R-) + field(ZRVL, "0") + field(ONST, "Center") #HOMF (R+C)-Offset P11, HOMR (R-C)+Offset P12 + field(ONVL, "1") + field(TWST, "Encoder") #HOMF (R+I), HOMR (R+I), Offsets + field(TWVL, "2") + field(THST, "Limit-Encoder") #HOMF (R+^I), HOMR (R-^I), Offsets + field(THVL, "3") + field(FRST, "Center-Encoder") #HOMF (R+C^I), HOMR (R-C^I), Offsets + field(FRVL, "4") + field(FVST, "Reference-Center") #HOMF RC+, HOMR RC-, Offsets + field(FVVL, "5") + field(SXST, "Ref-Center-Encoder") #HOMF RC+^I, HOMR RC-^I, Offsets + field(SXVL, "6") + field(PINI, "YES") +} + +################################################################################ +# P01: Controller provides the following modes of movement, however software +# limits are always monitored by the motor record, so only modes 0 and 1 are +# exposed to the user +# 0: Rotational movement +# 1: Hardware limit switches are monitored +# 2: Software limit switch are monitored +# 3: Hardware and software limit switches are monitored +################################################################################ +record(mbbo, "$(P)$(M)-MOVE-TYP_SET") +{ + field(DESC, "Move type") + field(DTYP, "asynInt32") + field(OUT, "@asyn($(PORT), $(ADDR), $(TIMEOUT))AXIS_MODE") + field(ZRST, "Rotational") + field(ZRVL, "0") + field(ONST, "HW switch") + field(ONVL, "1") + field(FLNK, "$(P)$(M)-MOVE-TYP_GET") + info(autosaveFields, "VAL") +} + +record(mbbi, "$(P)$(M)-MOVE-TYP_GET") +{ + field(DESC, "Move type") + field(DTYP, "asynInt32") + field(INP, "@asyn($(PORT), $(ADDR), $(TIMEOUT))AXIS_MODE") + field(ZRST, "Rotational") + field(ZRVL, "0") + field(ONST, "HW switch") + field(ONVL, "1") + field(PINI, "YES") +} + +############################################################################### +# P11: Mechanical zero point offset for limit switch direction + (away from +# LIMIT+ switch, towards LIMIT- switch) +################################################################################ +record(ao, "$(P)$(M)-POS-OFFSET_SET") +{ + field(DESC, "Positive offset") + field(DTYP, "asynInt32") + field(OUT, "@asyn($(PORT), $(ADDR), $(TIMEOUT))MOP_POS") + field(FLNK, "$(P)$(M)-POS-OFFSET_GET") + info(autosaveFields, "VAL") + +} + +record(ai, "$(P)$(M)-POS-OFFSET_GET") +{ + field(DESC, "Positive offset") + field(DTYP, "asynInt32") + field(INP, "@asyn($(PORT), $(ADDR), $(TIMEOUT))MOP_POS") + field(PINI, "YES") +} + +################################################################################ +# P12: Mechanical zero point offset for limit switch direction - (away from +# LIMIT- switch, towards LIMIT+ switch) +################################################################################ +record(ao, "$(P)$(M)-NEG-OFFSET_SET") +{ + field(DESC, "P12:Negative offset") + field(DTYP, "asynInt32") + field(OUT, "@asyn($(PORT), $(ADDR), $(TIMEOUT))MOP_NEG") + field(FLNK, "$(P)$(M)-NEG-OFFSET_GET") + info(autosaveFields, "VAL") +} + +record(ai, "$(P)$(M)-NEG-OFFSET_GET") +{ + field(DESC, "Negative offset") + field(DTYP, "asynInt32") + field(INP, "@asyn($(PORT), $(ADDR), $(TIMEOUT))MOP_NEG") + field(PINI, "YES") +} + +################################################################################ +# P13: Time lapse during initialization in miliseconds +################################################################################ +record(ao, "$(P)$(M)-INIT-TIMEOUT_SET") +{ + field(DESC, "Init time lapse") + field(DTYP, "asynInt32") + field(OUT, "@asyn($(PORT), $(ADDR), $(TIMEOUT))INIT_TIME") + field(EGU, "ms") + field(FLNK, "$(P)$(M)-INIT-TIMEOUT_GET") + info(autosaveFields, "VAL") +} + +record(ai, "$(P)$(M)-INIT-TIMEOUT_GET") +{ + field(DESC, "Init time lapse") + field(DTYP, "asynInt32") + field(INP, "@asyn($(PORT), $(ADDR), $(TIMEOUT))INIT_TIME") + field(EGU, "ms") + field(PINI, "YES") +} + +################################################################################ +# P16: Time lapse after positioning +################################################################################ +record(ao, "$(P)$(M)-POS-TIMEOUT_SET") +{ + field(DESC, "Position time lapse") + field(DTYP, "asynInt32") + field(OUT, "@asyn($(PORT), $(ADDR), $(TIMEOUT))POSITION_TIME") + field(EGU, "ms") + field(FLNK, "$(P)$(M)-POS-TIMEOUT_GET") + info(autosaveFields, "VAL") +} + +record(ai, "$(P)$(M)-POS-TIMEOUT_GET") +{ + field(DESC, "Position time lapse") + field(DTYP, "asynInt32") + field(INP, "@asyn($(PORT), $(ADDR), $(TIMEOUT))POSITION_TIME") + field(EGU, "ms") + field(PINI, "YES") +} + +################################################################################ +# P17: Defines when to use boost current (P42) +################################################################################ +record(mbbo, "$(P)$(M)-BOOST_SET") +{ + field(DESC, "Boost") + field(DTYP, "asynInt32") + field(OUT, "@asyn($(PORT), $(ADDR), $(TIMEOUT))BOOST") + field(ZRVL, "0") + field(ZRST, "OFF") + field(ONVL, "1") + field(ONST, "ON MOTOR RUN") + field(TWVL, "2") + field(TWST, "ON ACCELERATION") + field(FLNK, "$(P)$(M)-BOOST_GET") + info(autosaveFields, "VAL") +} + +record(mbbi, "$(P)$(M)-BOOST_GET") +{ + field(DESC, "Boost") + field(DTYP, "asynInt32") + field(INP, "@asyn($(PORT), $(ADDR), $(TIMEOUT))BOOST") + field(ZRVL, "0") + field(ZRST, "OFF") + field(ONVL, "1") + field(ONST, "ON MOTOR RUN") + field(TWVL, "2") + field(TWST, "ON ACCELERATION") + field(PINI, "YES") +} + +################################################################################ +# P27: Limit switch type +# NCC: Normally closed contact +# NOC: Normally open contact +################################################################################ +record(mbbo, "$(P)$(M)-SWITCH-TYP_SET") +{ + field(DESC, "Switch type") + field(DTYP, "asynInt32") + field(OUT, "@asyn($(PORT), $(ADDR), $(TIMEOUT))SWITCH_TYP") + field(ZRVL, "0") + field(ZRST, "NCC") + field(ONVL, "1") + field(ONST, "NOC") + field(FLNK, "$(P)$(M)-SWITCH-TYP_GET") + info(autosaveFields, "VAL") +} + +record(mbbi, "$(P)$(M)-SWITCH-TYP_GET") +{ + field(DESC, "Switch type") + field(DTYP, "asynInt32") + field(INP, "@asyn($(PORT), $(ADDR), $(TIMEOUT))SWITCH_TYP") + field(ZRVL, "0") + field(ZRST, "NCC") + field(ONVL, "1") + field(ONST, "NOC") + field(PINI, "YES") +} + +################################################################################ +# P28: AXIS Options +# 0 - Power stage is deactivated +# 1 - Power stage is activated +################################################################################ +record(bo, "$(P)$(M)-PWR-STAGE-MODE_SET") +{ + field(DESC, "Power stage ON/OFF") + field(DTYP, "asynInt32") + field(OUT, "@asyn($(PORT), $(ADDR), $(TIMEOUT))PWR_STAGE_MODE") + field(ZNAM, "Deactivate") + field(ONAM, "Activate") + field(FLNK, "$(P)$(M)-PWR-STAGE-MODE_GET") + info(autosaveFields, "VAL") +} + +record(bi, "$(P)$(M)-PWR-STAGE-MODE_GET") +{ + field(DESC, "Power stage ON/OFF") + field(DTYP, "asynInt32") + field(INP, "@asyn($(PORT), $(ADDR), $(TIMEOUT))PWR_STAGE_MODE") + field(ZNAM, "Deactivate") + field(ONAM, "Activate") + field(PINI, "YES") +} + +################################################################################ +# P34: Encoder type: +# 0: No encoder +# 1: Incremental +# 2: SSI Binary code +# 3: SSI Gray code + +################################################################################ +record(mbbo, "$(P)$(M)-ENC-TYP_SET") +{ + field(DESC, "Encoder type") + field(DTYP, "asynInt32") + field(OUT, "@asyn($(PORT), $(ADDR), $(TIMEOUT))ENCODER_TYP") + field(ZRST, "No encoder") + field(ZRVL, "0") + field(ONST, "Incremental") + field(ONVL, "1") + field(TWST, "SSI Binary") + field(TWVL, "2") + field(THST, "SSI Gray") + field(THVL, "3") + field(FLNK, "$(P)$(M)-ENC-TYP_GET") + info(autosaveFields, "VAL") +} + +record(mbbi, "$(P)$(M)-ENC-TYP_GET") +{ + field(DESC, "Encoder type") + field(DTYP, "asynInt32") + field(INP, "@asyn($(PORT), $(ADDR), $(TIMEOUT))ENCODER_TYP") + field(ZRST, "No encoder") + field(ZRVL, "0") + field(ONST, "Incremental") + field(ONVL, "1") + field(TWST, "SSI Binary") + field(TWVL, "2") + field(THST, "SSI Gray") + field(THVL, "3") + field(PINI, "YES") +} + +################################################################################ +# P35: SSI encoder resolution in bits. From 0b to 31b. +# 0b - the controller uses the resolution which is read from the connected instrument +################################################################################ +record(ao, "$(P)$(M)-ENC-RES_SET") +{ + field(DESC, "Encoder resolution") + field(DTYP, "asynInt32") + field(OUT, "@asyn($(PORT), $(ADDR), $(TIMEOUT))ENC_RESOLUTION") + field(EGU, "b") + field(HOPR, "31") + field(LOPR, "0") + field(FLNK, "$(P)$(M)-ENC-RES_GET")# + info(autosaveFields, "VAL") +} + +record(ai, "$(P)$(M)-ENC-RES_GET") +{ + field(DESC, "Encoder resolution") + field(DTYP, "asynInt32") + field(INP, "@asyn($(PORT), $(ADDR), $(TIMEOUT))ENC_RESOLUTION") + field(EGU, "b") + field(HOPR, "31") + field(LOPR, "0") + field(PINI, "YES") +} + +################################################################################ +# P36: Encoder function +# 0: counter +# 1: counter + SFI +# If P37 > 0, P36 is 1 else 0 +################################################################################ +record(bo, "$(P)$(M)-ENC-FUNC_SET") +{ + field(DESC, "SFI ON/OFF") + field(DTYP, "asynInt32") + field(OUT, "@asyn($(PORT), $(ADDR), $(TIMEOUT))ENC_FUNCTION") + field(ZNAM, "Counter") + field(ONAM, "Counter+SFI") + field(OMSL, "closed_loop") + field(DOL, "$(P)$(M)-ENC-SFI_GET") + field(FLNK, "$(P)$(M)-ENC-FUNC_GET") + info(autosaveFields, "VAL") +} + +record(bi, "$(P)$(M)-ENC-FUNC_GET") +{ + field(DESC, "SFI ON/OFF") + field(DTYP, "asynInt32") + field(INP, "@asyn($(PORT), $(ADDR), $(TIMEOUT))ENC_FUNCTION") + field(ZNAM, "Counter") + field(ONAM, "Counter+SFI") + field(PINI, "YES") +} + +################################################################################ +# P37: Encoder tolerance for SFI +################################################################################ +record(ao, "$(P)$(M)-ENC-SFI_SET") +{ + field(DESC, "SFI tolerance") + field(DTYP, "asynInt32") + field(OUT, "@asyn($(PORT), $(ADDR), $(TIMEOUT))ENC_SFI_WIDTH") + field(FLNK, "$(P)$(M)-ENC-SFI_GET") + info(autosaveFields, "VAL") +} + +record(ai, "$(P)$(M)-ENC-SFI_GET") +{ + field(DESC, "SFI tolerance") + field(DTYP, "asynInt32") + field(INP, "@asyn($(PORT), $(ADDR), $(TIMEOUT))ENC_SFI_WIDTH") + field(PINI, "YES") + field(FLNK, "$(P)$(M)-ENC-FUNC_SET") +} + +################################################################################ +# P38: Encoder direction of rotation +################################################################################ +record(bo, "$(P)$(M)-ENC-DIR_SET") +{ + field(DESC, "Encoder direction") + field(DTYP, "asynInt32") + field(OUT, "@asyn($(PORT), $(ADDR), $(TIMEOUT))ENC_DIRECTION") + field(ZNAM, "Positive") + field(ONAM, "Negative") + field(FLNK, "$(P)$(M)-ENC-DIR_GET") + info(autosaveFields, "VAL") +} + +record(bi, "$(P)$(M)-ENC-DIR_GET") +{ + field(DESC, "Encoder direction") + field(DTYP, "asynInt32") + field(INP, "@asyn($(PORT), $(ADDR), $(TIMEOUT))ENC_DIRECTION") + field(ZNAM, "Positive") + field(ONAM, "Negative") +} + +################################################################################ +# P40: Stop current in mA. Can be set in 10 mA increments +################################################################################ +record(ao, "$(P)$(M)-STOP-CURRENT_SET") +{ + field(DESC, "Stop current") + field(DTYP, "asynInt32") + field(OUT, "@asyn($(PORT), $(ADDR), $(TIMEOUT))STOP_CURRENT") + field(EGU, "mA") + field(HOPR, "2500") + field(LOPR, "0") + field(HIGH, "2200") + field(HIHI, "2400") + field(HSV, "MINOR") + field(HHSV, "MAJOR") + field(FLNK, "$(P)$(M)-STOP-CURRENT_GET") + info(autosaveFields, "VAL") + +} + +record(ai, "$(P)$(M)-STOP-CURRENT_GET") +{ + field(DESC, "Stop current") + field(DTYP, "asynInt32") + field(INP, "@asyn($(PORT), $(ADDR), $(TIMEOUT))STOP_CURRENT") + field(EGU, "mA") + field(HOPR, "2500") + field(LOPR, "0") + field(HIGH, "2200") + field(HIHI, "2400") + field(HSV, "MINOR") + field(HHSV, "MAJOR") + field(PINI, "YES") + +} + +################################################################################ +# P41: Run current in mA. Can be set in 10 mA increments +################################################################################ +record(ao, "$(P)$(M)-RUN-CURRENT_SET") +{ + field(DESC, "Run current") + field(DTYP, "asynInt32") + field(OUT, "@asyn($(PORT), $(ADDR), $(TIMEOUT))RUN_CURRENT") + field(EGU, "mA") + field(HOPR, "2500") + field(LOPR, "0") + field(HIGH, "2200") + field(HIHI, "2400") + field(HSV, "MINOR") + field(HHSV, "MAJOR") + field(FLNK, "$(P)$(M)-RUN-CURRENT_GET") + info(autosaveFields, "VAL") + +} + +record(ai, "$(P)$(M)-RUN-CURRENT_GET") +{ + field(DESC, "Run current") + field(DTYP, "asynInt32") + field(INP, "@asyn($(PORT), $(ADDR), $(TIMEOUT))RUN_CURRENT") + field(EGU, "mA") + field(HOPR, "2500") + field(LOPR, "0") + field(HIGH, "2200") + field(HIHI, "2400") + field(HSV, "MINOR") + field(HHSV, "MAJOR") + field(PINI, "YES") + +} + +################################################################################ +# P42: Boost current in mA. Can be set in 10 mA increments +################################################################################ +record(ao, "$(P)$(M)-BOOST-CURRENT_SET") +{ + field(DESC, "Boost current") + field(DTYP, "asynInt32") + field(OUT, "@asyn($(PORT), $(ADDR), $(TIMEOUT))BOOST_CURRENT") + field(EGU, "mA") + field(HOPR, "2500") + field(LOPR, "0") + field(HIGH, "2200") + field(HIHI, "2400") + field(HSV, "MINOR") + field(HHSV, "MAJOR") + field(FLNK, "$(P)$(M)-BOOST-CURRENT_GET") + info(autosaveFields, "VAL") + +} + +record(ai, "$(P)$(M)-BOOST-CURRENT_GET") +{ + field(DESC, "Boost current") + field(DTYP, "asynInt32") + field(INP, "@asyn($(PORT), $(ADDR), $(TIMEOUT))BOOST_CURRENT") + field(EGU, "mA") + field(HOPR, "2500") + field(LOPR, "0") + field(HIGH, "2200") + field(HIHI, "2400") + field(HSV, "MINOR") + field(HHSV, "MAJOR") + field(PINI, "YES") +} + +################################################################################ +# P43: Current hold time in msec +################################################################################ +record(ao, "$(P)$(M)-CURRENT-DELAY_SET") +{ + field(DESC, "Current hold time") + field(DTYP, "asynInt32") + field(OUT, "@asyn($(PORT), $(ADDR), $(TIMEOUT))CURRENT_DELAY_TIME") + field(EGU, "msec") + field(FLNK, "$(P)$(M)-CURRENT-DELAY_GET") + info(autosaveFields, "VAL") + +} + +record(ai, "$(P)$(M)-CURRENT-DELAY_GET") +{ + field(DESC, "Current hold time") + field(DTYP, "asynInt32") + field(INP, "@asyn($(PORT), $(ADDR), $(TIMEOUT))CURRENT_DELAY_TIME") + field(EGU, "mA") + field(PINI, "YES") +} + + +################################################################################ +# P45: Step resolution +################################################################################ +record(mbbo, "$(P)$(M)-STEP-RES_SET") +{ + field(DESC, "Step resolution") + field(DTYP, "asynInt32") + field(OUT, "@asyn($(PORT), $(ADDR), $(TIMEOUT))STEP_RES") + field(ZRST, "1") + field(ZRVL, "1") + field(ONST, "1/2") + field(ONVL, "2") + field(TWST, "1/4") + field(TWVL, "4") + field(THST, "1/8") + field(THVL, "8") + field(FRST, "1/10") + field(FRVL, "10") + field(FVST, "1/16") + field(FVVL, "16") + field(SXST, "1/128") + field(SXVL, "128") + field(SVST, "1/256") + field(SVVL, "256") + field(FLNK, "$(P)$(M)-STEP-RES_GET") + info(autosaveFields, "VAL") +} + +record(mbbi, "$(P)$(M)-STEP-RES_GET") +{ + field(DESC, "Step resolution") + field(DTYP, "asynInt32") + field(INP, "@asyn($(PORT), $(ADDR), $(TIMEOUT))STEP_RES") + field(ZRST, "1") + field(ZRVL, "1") + field(ONST, "1/2") + field(ONVL, "2") + field(TWST, "1/4") + field(TWVL, "4") + field(THST, "1/8") + field(THVL, "8") + field(FRST, "1/10") + field(FRVL, "10") + field(FVST, "1/16") + field(FVVL, "16") + field(SXST, "1/128") + field(SXVL, "128") + field(SVST, "1/256") + field(SVVL, "256") + field(PINI, "YES") +} + +################################################################################ +# P49: Power stage temperature +################################################################################ +record(ai, "$(P)$(M)-PS-TEMPERATURE") +{ + field(DESC, "Power stage temp") + field(DTYP, "asynFloat64") + field(INP, "@asyn($(PORT), $(ADDR), $(TIMEOUT))PS_TEMPERATURE") + field(EGU, "°C") + field(SCAN, "$(SCAN)") + field(PREC, 1) + field(PINI, "YES") +} + +################################################################################ +# Brake output (digital output) for releasing motor brake +################################################################################ +record(ao, "$(P)$(M)-BRAKE-OUTPUT") +{ + field(DESC, "Brake output") + field(DTYP, "asynFloat64") + field(OUT, "@asyn($(PORT), $(ADDR), $(TIMEOUT))AXIS_BRAKE_OUTPUT") + info(asyn:READBACK, "1") +} + +################################################################################ +# Disable motor output when idle +################################################################################ +record(bo, "$(P)$(M)-DISABLE_MOTOR") +{ + field(DESC, "Disables motor output when idle") + field(DTYP, "asynInt32") + field(OUT, "@asyn($(PORT), $(ADDR), $(TIMEOUT))AXIS_DISABLE_MOTOR") + field(ZNAM, "Motor enabled") + field(ONAM, "Motor disabled on idle") + info(asyn:READBACK, "1") + info(autosaveFields, "VAL") +} + +################################################################################ +# Time to engage brake (seconds) +################################################################################ +record(ao, "$(P)$(M)-BRAKE-ENGAGE-TIME") +{ + field(DESC, "Brake engage time") + field(DTYP, "asynFloat64") + field(EGU, "sec") + field(OUT, "@asyn($(PORT), $(ADDR), $(TIMEOUT))AXIS_BRAKE_ENGAGE_TIME") + info(asyn:READBACK, "1") + info(autosaveFields, "VAL") +} + +################################################################################ +# Time to release brake (seconds) +################################################################################ +record(ao, "$(P)$(M)-BRAKE-RELEASE-TIME") +{ + field(DESC, "Brake release time") + field(DTYP, "asynFloat64") + field(EGU, "sec") + field(OUT, "@asyn($(PORT), $(ADDR), $(TIMEOUT))AXIS_BRAKE_RELEASE_TIME") + info(asyn:READBACK, "1") + info(autosaveFields, "VAL") +} diff --git a/phytronApp/Db/Phytron_MCM01.db b/phytronApp/Db/Phytron_MCM01.db index d53ad2e..e63394c 100644 --- a/phytronApp/Db/Phytron_MCM01.db +++ b/phytronApp/Db/Phytron_MCM01.db @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: EPICS ################################################################################ # This database contains records used to monitor status and reset the MCM01 # controller. @@ -220,4 +221,3 @@ record(bo, "$(P)-RESET-STATUS") field(ONAM, "RESET") field(ZNAM, "RESET") } - diff --git a/phytronApp/Db/Phytron_motor.db b/phytronApp/Db/Phytron_motor.db index cd50cfb..fb9c02c 100644 --- a/phytronApp/Db/Phytron_motor.db +++ b/phytronApp/Db/Phytron_motor.db @@ -1,23 +1,32 @@ +# SPDX-License-Identifier: EPICS record(motor,"$(P)$(M)") { - info(autosaveFields, "DVAL DIR VELO VBAS VMAX ACCL BDST BVEL BACC MRES ERES PREC EGU DHLM DLLM OFF") + info(autosaveFields, "DVAL DIR VELO VBAS VMAX ACCL BDST BVEL BACC MRES ERES SREV UREV PREC EGU DHLM DLLM OFF") field(DESC,"$(DESC)") - field(DTYP,"$(DTYP)") + field(DTYP,"$(DTYP=asynMotor)") field(DIR,"$(DIR)") field(VELO,"$(VELO)") field(VBAS,"$(VBAS)") field(VMAX,"$(VMAX)") field(ACCL,"$(ACCL)") + field(ACCU,"$(ACCU=0)") field(BDST,"$(BDST)") field(BVEL,"$(BVEL)") field(BACC,"$(BACC)") field(OUT,"@asyn($(PORT),$(ADDR))") - field(MRES,"$(MRES)") + field(MRES,"$(MRES=0)") field(ERES,"$(ERES)") + field(SREV,"$(SREV=0)") + field(UREV,"$(UREV=0)") + field(UEIP,"$(UEIP=Yes)") field(PREC,"$(PREC)") field(EGU,"$(EGU)") + field(OFF,"$(OFF=0)") field(DHLM,"$(DHLM)") field(DLLM,"$(DLLM)") field(INIT,"$(INIT)") - field(TWV,"1") + field(RDBD,"$(RDBD=0)") + field(RTRY,"$(RTRY=0)") + field(MDEL,"$(MDEL=)") + field(TWV,"$(TWV=1)") } diff --git a/phytronApp/op/opi/PhytronI1AM01.opi b/phytronApp/op/opi/PhytronI1AM01.opi index 69cedcf..d244574 100644 --- a/phytronApp/op/opi/PhytronI1AM01.opi +++ b/phytronApp/op/opi/PhytronI1AM01.opi @@ -1,4 +1,5 @@ + false @@ -3386,4 +3387,4 @@ $(pv_value) $(pv_value) 662 - \ No newline at end of file + diff --git a/phytronApp/op/opi/PhytronI1AM01Status.opi b/phytronApp/op/opi/PhytronI1AM01Status.opi index b65ed0d..1daca18 100644 --- a/phytronApp/op/opi/PhytronI1AM01Status.opi +++ b/phytronApp/op/opi/PhytronI1AM01Status.opi @@ -1,4 +1,5 @@ + false @@ -2237,4 +2238,4 @@ ERROR 11 - \ No newline at end of file + diff --git a/phytronApp/op/opi/PhytronMCM01Status.opi b/phytronApp/op/opi/PhytronMCM01Status.opi index c86756b..7c6e0d0 100644 --- a/phytronApp/op/opi/PhytronMCM01Status.opi +++ b/phytronApp/op/opi/PhytronMCM01Status.opi @@ -1,4 +1,5 @@ + false @@ -1579,4 +1580,4 @@ AVAILABLE 8 - \ No newline at end of file + diff --git a/phytronApp/op/opi/PhytronMain.opi b/phytronApp/op/opi/PhytronMain.opi index 67d3d03..30912cd 100644 --- a/phytronApp/op/opi/PhytronMain.opi +++ b/phytronApp/op/opi/PhytronMain.opi @@ -1,4 +1,5 @@ + false @@ -438,4 +439,4 @@ $(pv_value) $(pv_value) 296 - \ No newline at end of file + diff --git a/phytronApp/op/opi/Phytron_AIO.bob b/phytronApp/op/opi/Phytron_AIO.bob new file mode 100644 index 0000000..f476ed8 --- /dev/null +++ b/phytronApp/op/opi/Phytron_AIO.bob @@ -0,0 +1,253 @@ + + + + $(P):$(PORT) analog I/O + 1405 + 251 + 360 + 60 + + + + + false + + EDM activeRectangleClass + 185 + 176 + 21 + 1 + + + + + + + + + + + EDM activeRectangleClass + 181 + 21 + 1 + + + + + + + + + + + EDM activeRectangleClass + =highestSeverity(`$(PV)`) + 21 + 182 + 32 + true + false + + + 0 + + + + + + + + 1 + + + + + + + + 2 + + + + + + + + 3 + + + + + + + + 4 + + + + + + + + + + EDM activeXTextDspClass_noedit + $(P):setVolt$(PORT).DESC + 2 + 2 + 89 + 14 + + + + + + + + + + + + + true + false + 1 + + + EDM activeXTextDspClass_noedit + $(P):rdVolt$(PORT) + 200 + 30 + 80 + 14 + + + + + + + + + + + + + 1 + + + EDM activeMotifSliderClass + $(P):setVolt$(PORT) + 1 + 23 + 178 + 28 + + + + + + + + + + + + + + + EDM activeXTextDspClass_noedit + $(P):rdVolt$(PORT).DESC + 200 + 3 + 89 + 14 + + + + + + + + + + + + + true + false + 1 + + + EDM activeXTextDspClass_noedit + $(P):setVolt$(PORT).RVAL + 101 + 3 + 76 + 14 + + + + + + + + + + + + + true + false + 1 + + + EDM activeXTextDspClass_noedit + $(P):rdVolt$(PORT).RVAL + 300 + 30 + 50 + 14 + + + + + + + + + + + + + 1 + + + EDM activeMessageButtonClass + + + $(pv_name) + 1 + PROC + + + $(P):rdVolt$(PORT).PROC + PROC + 300 + 3 + 35 + 14 + + + + + + + + + + + + + + diff --git a/phytronApp/op/opi/Phytron_AIO_MCC.bob b/phytronApp/op/opi/Phytron_AIO_MCC.bob new file mode 100644 index 0000000..be3572c --- /dev/null +++ b/phytronApp/op/opi/Phytron_AIO_MCC.bob @@ -0,0 +1,143 @@ + + + + $(P):$(PORT) analog I/O + 1405 + 251 + 215 + 50 + + + + + false + + EDM activeRectangleClass + 215 + 21 + 1 + + + + + + + + + + + EDM activeXTextDspClass_noedit + $(P):rdVolt$(PORT) + 3 + 30 + 101 + 14 + + + + + + + + + + + + + 1 + + + EDM activeXTextDspClass_noedit + $(P):rdVolt$(PORT).DESC + 2 + 3 + 160 + 14 + + + + + + + + + + + + + true + false + 1 + + + EDM activeXTextDspClass_noedit + $(P):rdVolt$(PORT).RVAL + 111 + 30 + 50 + 14 + + + + + + + + + + + + + 1 + + + EDM activeMessageButtonClass + + + $(pv_name) + 1 + PROC + + + $(P):rdVolt$(PORT).PROC + PROC + 174 + 3 + 35 + 14 + + + + + + + + + + + + + + + EDM activeXTextDspClass_noedit + $(P):rdVolt$(PORT).RVAL + 164 + 30 + 50 + 14 + + + + + + + + + + + + + 4 + 1 + + diff --git a/phytronApp/op/opi/Phytron_CMD.bob b/phytronApp/op/opi/Phytron_CMD.bob new file mode 100644 index 0000000..2468f13 --- /dev/null +++ b/phytronApp/op/opi/Phytron_CMD.bob @@ -0,0 +1,400 @@ + + + + $(P) Phytron command + 334 + 170 + 650 + 40 + + + + + false + + EDM activeRectangleClass + 650 + 21 + 1 + + + + + + + + + + + EDM activeRectangleClass + =highestSeverity(`$(P):directcommand`) + 3 + 20 + 175 + 19 + true + false + + + 0 + + + + + + + + 1 + + + + + + + + 2 + + + + + + + + 3 + + + + + + + + 4 + + + + + + + + + + EDM activeXTextDspClass_noedit + $(P):directcommand.DESC + 2 + 2 + 170 + 14 + + + + + + + + + + + + + true + false + 1 + + + EDM activeRectangleClass + 541 + 20 + 103 + 21 + 1 + + + + + + + + + + + EDM activeRectangleClass + =highestSeverity(`$(P):directreply`) + 183 + 20 + 175 + 19 + true + false + + + 0 + + + + + + + + 1 + + + + + + + + 2 + + + + + + + + 3 + + + + + + + + 4 + + + + + + + + + + EDM activeXTextDspClass_noedit + $(P):directreply + 185 + 22 + 170 + 14 + + + + + + + + + + + + + false + 1 + + + EDM activeXTextDspClass_noedit + $(P):directreply.DESC + 182 + 2 + 170 + 14 + + + + + + + + + + + + + true + false + 1 + + + EDM activeRectangleClass + =highestSeverity(`$(P):directstatus`) + 363 + 20 + 175 + 19 + true + false + + + 0 + + + + + + + + 1 + + + + + + + + 2 + + + + + + + + 3 + + + + + + + + 4 + + + + + + + + + + EDM activeXTextDspClass_noedit + $(P):directreplyInt + 365 + 22 + 84 + 14 + + + + + + + + + + + + + 1 + + + EDM activeXTextDspClass_noedit + $(P):directreplyInt.DESC + 362 + 2 + 170 + 14 + + + + + + + + + + + + + true + false + 1 + + + EDM activeXTextDspClass_noedit + $(P):directreplyInt + 453 + 22 + 81 + 14 + + + + + + + + + + + + + 4 + 1 + + + EDM activeXTextDspClass + $(P):directcommand + 5 + 22 + 170 + 14 + + + + + + + + + + + + + false + + + EDM activeXTextDspClass_noedit + $(P):directstatus + 541 + 22 + 103 + 14 + + + + + + + + + + + + + false + 1 + + + EDM activeXTextDspClass_noedit + $(P):directstatus.DESC + 541 + 2 + 103 + 14 + + + + + + + + + + + + + true + false + 1 + + diff --git a/phytronApp/op/opi/Phytron_DIO.bob b/phytronApp/op/opi/Phytron_DIO.bob new file mode 100644 index 0000000..2002e32 --- /dev/null +++ b/phytronApp/op/opi/Phytron_DIO.bob @@ -0,0 +1,165 @@ + + + + $(P):$(PORT) digital I/O + 1020 + 281 + 190 + 50 + + + + + false + + EDM activeRectangleClass + 190 + 21 + 1 + + + + + + + + + + + EDM activeRectangleClass + =highestSeverity(`$(P):setDO$(PORT)`) + 21 + 77 + 23 + true + false + + + 0 + + + + + + + + 1 + + + + + + + + 2 + + + + + + + + 3 + + + + + + + + 4 + + + + + + + + + + EDM activeXTextDspClass_noedit + $(P):setDO$(PORT).DESC + 2 + 2 + 89 + 14 + + + + + + + + + + + + + true + false + 1 + + + EDM activeMessageButtonClass + + + $(pv_name) + 1 + PROC + + + $(P):setDO$(PORT).PROC + PROC + 145 + 2 + 35 + 14 + + + + + + + + + + + + + + + EDM activeXTextDspClass + $(P):setDO$(PORT) + 2 + 21 + 75 + 21 + + + + + + + + + + + + + false + + + Text Update DO + $(P):rbkDO$(PORT) + 79 + 22 + 50 + + + Text Update DI + $(P):rdDI$(PORT) + 139 + 22 + 50 + + diff --git a/phytronApp/op/opi/Phytron_DIO_MCC1.bob b/phytronApp/op/opi/Phytron_DIO_MCC1.bob new file mode 100644 index 0000000..3634322 --- /dev/null +++ b/phytronApp/op/opi/Phytron_DIO_MCC1.bob @@ -0,0 +1,209 @@ + + + + $(P):$(PORT) MCC-1 digital I/O + 1020 + 281 + 270 + 50 + + + + + false + + EDM activeRectangleClass + 270 + 21 + 1 + + + + + + + + + + + EDM activeRectangleClass + =highestSeverity(`$(P):setDO$(PORT)`) + 21 + 77 + 23 + true + false + + + 0 + + + + + + + + 1 + + + + + + + + 2 + + + + + + + + 3 + + + + + + + + 4 + + + + + + + + + + EDM activeXTextDspClass_noedit + $(P):setDO$(PORT).DESC + 2 + 2 + 130 + 14 + + + + + + + + + + + + + true + false + 1 + + + EDM activeMessageButtonClass + + + $(pv_name) + 1 + PROC + + + $(P):setDO$(PORT).PROC + PROC + 130 + 2 + 35 + 14 + + + + + + + + + + + + + + + EDM activeXTextDspClass + $(P):setDO$(PORT) + 2 + 21 + 75 + 21 + + + + + + + + + + + + + false + + + Text Update DO + $(P):rbkDO$(PORT) + 79 + 22 + 50 + + + Text Update DI + $(P):rdDI$(PORT) + 139 + 22 + 50 + + + Test Update direction + $(P):dir$(PORT) + 193 + 21 + 75 + 21 + + + + + + + + + + + + + false + + + EDM activeXTextDspClass_noedit_1 + $(P):dir$(PORT).DESC + 174 + 2 + 90 + 14 + + + + + + + + + + + + + true + false + 1 + + diff --git a/phytronApp/op/opi/Phytron_MCC.bob b/phytronApp/op/opi/Phytron_MCC.bob new file mode 100644 index 0000000..ea61182 --- /dev/null +++ b/phytronApp/op/opi/Phytron_MCC.bob @@ -0,0 +1,1003 @@ + + + $(P)$(M) Phytron MCC axis + 500 + + + + + + + 6 + 6 + + Label + AXIS MODE + 17 + 129 + 80 + 26 + 1 + + + + + + + + + Menu Button + $(P)$(M)-MOVE-TYP_SET + 116 + 132 + 134 + 23 + + + + + false + + + Label_1 + HOMING MODE + 17 + 160 + 97 + 34 + 1 + + + + + + + + + Menu Button_1 + $(P)$(M)-HOMING_SET + 116 + 170 + 134 + 23 + + + + + false + + + Text Update + $(P)$(M)-MOVE-TYP_GET + 260 + 132 + 128 + 26 + 1 + false + + + + + + + + + Text Update_1 + $(P)$(M)-HOMING_GET + 260 + 170 + 128 + 26 + 1 + false + + + + + + + + + Label_2 + POSITIVE OFFSET + 419 + 56 + 111 + 26 + 1 + + + + + + + + + Label_3 + NEGATIVE OFFSET + 419 + 94 + 111 + 26 + 1 + + + + + + + + + Text_1 + $(P)$(M)-POS-OFFSET_SET + 539 + 56 + 26 + + + + + + + + + + + + + Text Update_2 + $(P)$(M)-POS-OFFSET_GET + 662 + 56 + 128 + 26 + 1 + false + + + + + + + + + Text Update_3 + $(P)$(M)-NEG-OFFSET_GET + 662 + 94 + 128 + 26 + 1 + false + + + + + + + + + Label_4 + STOP CURRENT + 419 + 245 + 111 + 26 + 1 + + + + + + + + + Text_2 + $(P)$(M)-STOP-CURRENT_SET + 539 + 245 + 26 + + + + + + + + + + + + + Text Update_4 + $(P)$(M)-STOP-CURRENT_GET + 662 + 245 + 128 + 26 + 1 + false + + + + + + + + + Label_5 + RUN CURRENT + 419 + 283 + 111 + 26 + 1 + + + + + + + + + Text_3 + $(P)$(M)-RUN-CURRENT_SET + 539 + 283 + 26 + + + + + + + + + + + + + Text Update_5 + $(P)$(M)-RUN-CURRENT_GET + 662 + 283 + 128 + 26 + 1 + false + + + + + + + + + Label_6 + BOOST CURRENT + 419 + 321 + 111 + 26 + 1 + + + + + + + + + Text_4 + $(P)$(M)-BOOST-CURRENT_SET + 539 + 321 + 26 + + + + + + + + + + + + + Text Update_6 + $(P)$(M)-BOOST-CURRENT_GET + 662 + 321 + 128 + 26 + 1 + false + + + + + + + + + Label_7 + STEP RES. + 17 + 206 + 80 + 26 + 1 + + + + + + + + + Menu Button_2 + $(P)$(M)-STEP-RES_SET + 116 + 208 + 134 + 23 + + + + + false + + + Text Update_7 + $(P)$(M)-STEP-RES_GET + 260 + 208 + 128 + 26 + 1 + false + + + + + + + + + Label_8 + STATUS + 17 + 56 + 56 + 26 + 1 + + + + + + + + + Action Button + + + Phytron_MCCStatus.bob + tab + + + MORE + 145 + 56 + 105 + 26 + + + + + $(actions) + false + + + Label_9 + ENCODER TYPE + 17 + 276 + 97 + 34 + 1 + + + + + + + + + Menu Button_3 + $(P)$(M)-ENC-TYP_SET + 116 + 284 + 134 + 23 + + + + + false + + + Text Update_9 + $(P)$(M)-ENC-TYP_GET + 260 + 284 + 128 + 26 + 1 + false + + + + + + + + + Label_10 + INITIALIZATION +TIME LAPSE + 419 + 129 + 111 + 33 + 1 + + + + + + + + + Text_5 + $(P)$(M)-INIT-TIMEOUT_SET + 539 + 132 + 26 + + + + + + + + + + + + + Text Update_10 + $(P)$(M)-INIT-TIMEOUT_GET + 662 + 132 + 128 + 26 + 1 + false + + + + + + + + + Label_11 + POSITIONING +TIME LAPSE + 419 + 170 + 111 + 33 + 1 + + + + + + + + + Text_6 + $(P)$(M)-POS-TIMEOUT_SET + 539 + 170 + 26 + + + + + + + + + + + + + Text Update_11 + $(P)$(M)-POS-TIMEOUT_GET + 662 + 170 + 128 + 26 + 1 + false + + + + + + + + + Label_14 + SWITCH TYPE + 17 + 359 + 24 + 1 + + + + + + + + + Menu Button_5 + $(P)$(M)-SWITCH-TYP_SET + 116 + 360 + 134 + 23 + + + + + false + + + Text Update_14 + $(P)$(M)-SWITCH-TYP_GET + 260 + 358 + 128 + 26 + 1 + false + + + + + + + + + Label_15 + POWER STAGE + 17 + 397 + 111 + 24 + 1 + + + + + + + + + Menu Button_6 + $(P)$(M)-PWR-STAGE-MODE_SET + 116 + 398 + 134 + 23 + + + + + false + + + Text Update_15 + $(P)$(M)-PWR-STAGE-MODE_GET + 260 + 398 + 128 + 26 + 1 + false + + + + + + + + + Label_16 + SSI RESOLUTION + 419 + 348 + 121 + 40 + 1 + + + + + + + + + Text_8 + $(P)$(M)-ENC-RES_SET + 539 + 359 + 26 + + + + + + + false + + + + + + + Text Update_16 + $(P)$(M)-ENC-RES_GET + 662 + 359 + 128 + 26 + 1 + false + + + + + + + + + Label_18 + CURRENT HOLD TIME + 419 + 393 + 111 + 33 + 1 + + + + + + + + + Text_10 + $(P)$(M)-CURRENT-DELAY_SET + 539 + 397 + 26 + + + + + + + + + + + + + Text Update_19 + $(P)$(M)-CURRENT-DELAY_GET + 662 + 397 + 128 + 26 + 1 + false + + + + + + + + + Label_19 + POWER STAGE +TEMPERATURE + 17 + 429 + 111 + 33 + 1 + + + + + + + + + Text Update_20 + $(P)$(M)-PS-TEMPERATURE + 260 + 436 + 128 + 26 + 1 + false + + + + + + + + + Text_11 + $(P)$(M)-NEG-OFFSET_SET + 539 + 94 + 26 + + + + + + + + + + + + + Label_12 + BOOST + 17 + 245 + 111 + 26 + 1 + + + + + + + + + Text Update_12 + $(P)$(M)-BOOST_GET + 260 + 246 + 128 + 26 + 1 + false + + + + + + + + + Menu Button_8 + $(P)$(M)-BOOST_SET + 116 + 246 + 134 + 23 + + + + + false + + + LED + $(P)$(M)-AXIS-ERR + 0 + 116 + 58 + 22 + 22 + + + + + + + + + Label_23 + $(P)$(M) + 17 + 9 + 171 + 44 + + + + + 1 + + + + + + + + + Label_24 + ENCODER DIRECTION + 18 + 314 + 97 + 34 + 1 + + + + + + + + + Text Update_24 + $(P)$(M)-ENC-DIR_GET + 260 + 322 + 128 + 26 + 1 + false + + + + + + + + + Menu Button_9 + $(P)$(M)-ENC-DIR_SET + 116 + 322 + 134 + 23 + + + + + false + + + Label_26 + RESET + 17 + 94 + 56 + 26 + 1 + + + + + + + + + Action Button_1 + + + $(P)$(M)-RESET + 1 + + + $(P)$(M)-RESET + RESET AXIS + 260 + 95 + 128 + 23 + + + + + false + + + Action Button_4 + + + $(P)$(M)-RESET-STATUS + 1 + + + RESET STATUS + 116 + 94 + 134 + 25 + + + + + $(actions) + false + + + Label_27 + ENCODER SFI + 419 + 208 + 111 + 26 + 1 + + + + + + + + + Text_12 + $(P)$(M)-ENC-SFI_SET + 539 + 208 + 26 + + + + + + + + + + + + + Text Update_25 + $(P)$(M)-ENC-SFI_GET + 662 + 208 + 128 + 26 + 1 + false + + + + + + + + diff --git a/phytronApp/op/opi/Phytron_MCCStatus.bob b/phytronApp/op/opi/Phytron_MCCStatus.bob new file mode 100644 index 0000000..99f8800 --- /dev/null +++ b/phytronApp/op/opi/Phytron_MCCStatus.bob @@ -0,0 +1,184 @@ + + + + $(P)$(M) MCC axis status + 180 + 270 + + + + + + + 6 + 6 + + LED + $(P)$(M)-AXIS-BUSY + 0 + 149 + 65 + 22 + 22 + + + + + + + + + LED_4 + $(P)$(M)-LS-POS + 0 + 149 + 105 + 22 + 22 + + + + + + + + + LED_5 + $(P)$(M)-LS-NEG + 0 + 149 + 145 + 22 + 22 + + + + + + + + + LED_6 + $(P)$(M)-LS-CNT + 0 + 149 + 185 + 22 + 22 + + + + + + + + + LED_11 + $(P)$(M)-PS-ERR + 0 + 149 + 225 + 22 + 22 + + + + + + + + + Label + AXIS BUSY + 5 + 63 + 115 + 26 + 1 + + + + + + + + + Label_4 + LIMIT SWITCH + + 5 + 103 + 131 + 26 + 1 + + + + + + + + + Label_5 + LIMIT SWITCH - + 5 + 143 + 131 + 26 + 1 + + + + + + + + + Label_6 + LIMIT SWITCH CENTER + 5 + 183 + 143 + 26 + 1 + + + + + + + + + Label_13 + AXIS POWER STAGE ERROR + 5 + 223 + 131 + 33 + 1 + + + + + + + + + Label_25 + $(P)$(M) + 11 + 14 + 137 + 44 + + + + + 1 + + + + + + + + diff --git a/phytronApp/src/Makefile b/phytronApp/src/Makefile index 874a5e6..eee84a8 100644 --- a/phytronApp/src/Makefile +++ b/phytronApp/src/Makefile @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: EPICS TOP=../.. include $(TOP)/configure/CONFIG @@ -10,8 +11,11 @@ DBD += phytronSupport.dbd # The following are compiled and added to the support library phytronAxisMotor_SRCS += phytronAxisMotor.cpp +phytronAxisMotor_SRCS += phytronIOctrl.cpp +phytronAxisMotor_SRCS += phytronSupport.cpp INC += phytronAxisMotor.h +INC += phytronIOctrl.h phytronAxisMotor_LIBS += motor phytronAxisMotor_LIBS += asyn diff --git a/phytronApp/src/README.txt b/phytronApp/src/README.txt index de9d609..70df1cc 100644 --- a/phytronApp/src/README.txt +++ b/phytronApp/src/README.txt @@ -1,13 +1,19 @@ ******************************************************************************** -Phytron I1AM01 Stepper Motor Controller Asyn Driver Documentation +Phytron phyMOTION I1AM01, I1AM02, I1EM01, I1EM02 Stepper Motor Controller +Phytron MCC-1, MCC-2 Stepper Motor Controller with I/O +Phytron DIOM01.1, AIOM01.1 Digital and Analog I/O Controller +Asyn Driver Documentation +SPDX-License-Identifier: EPICS Authors: Tom Slejko, Bor Marolt, Cosylab d.d. tom.slejko@cosylab.com bor.marolt@cosylab.com Lutz Rossa, Helmholtz-Zentrum Berlin fuer Materialien und Energy GmbH - Will Smith, Helmholtz-Zentrum Berlin fuer Materialien und Energy GmbH - rossa@helmholtz-berlin.de - william.smith@helmholtz-berlin.de + + Will Smith, Helmholtz-Zentrum Berlin fuer Materialien und Energy GmbH + + Bernhard Kuner, Helmholtz-Zentrum Berlin fuer Materialien und Energy GmbH + ******************************************************************************** Table of contents: @@ -15,21 +21,28 @@ Table of contents: - Example Application - New Applicaion - Database - - Supported I1AM01 Features + - Single motor axis features + - Supported devices - Initialization records - Status - Homing + - Brake support - Reset - - I1AM01 parameters + - List of parameters - Supported MCM01 Features - Motor record - - List of remaining I1AM01 parameters handled by the motor record, - internally by controller, or not applicable + - Supported analog and digital I/O + - Supported direct command interface + - List of remaining I1AM01, I1AM02, I1EM01, I1EM02, MCC-1, MCC-2 parameters + handled by the motor record, internally by controller, or not applicable - GUI +- Asyn option interface +================================= Controller and axis configuration ================================= + Example application: -------------------- Motor record includes an example Phytron application. Before using it do: @@ -54,6 +67,7 @@ The following dbd files and libraries must be added to the $(APP)/src/Makefile: test_DBD += asyn.dbd test_DBD += motorSupport.dbd test_DBD += drvAsynIPPort.dbd +test_DBD += drvAsynSerialPort.dbd # for use of USB communication test_DBD += phytron.dbd test_LIBS += asyn @@ -63,16 +77,13 @@ test_LIBS += phytronAxisMotor An example motor.substitutions.phytron and st.cmd.phytron files are located in the $(MOTOR_RECORD)/iocBoot/iocWithAsyn/ directory. -******************************************************************************** -WARNING: For the controller to work properly all three database files ( -Phytron_I1AM01.db, Phytron_MCM01.db and Phytron_motor.db) must be used. -******************************************************************************** - Start up script must perform the following: Before configuring the controller the user must create an asyn port by running drvAsynIPPortConfigure or drvAsynSerialPortConfigure in order to create a -communication interface to Phytron's MCM Unit which controlls the I1AM01 -modules. +communication interface to Phytron's MCM Unit which controlls the I1AM01, +I1AM02, I1EM01 and I1EM02 modules or Phytron MCC Unit which controlls the MCC-1 +or MCC-2 axes. Below any type of I1AM01 means also the other types I1AM02, +I1EM01, I1EM02, MCC-1 and MCC-2. If serial port is used for communication with the controller, baud rate, number of data bits, parity, number of stop bits and End Of Line character must be set, @@ -88,28 +99,38 @@ asynSetOption ("testRemote", -1, "stop", "1") If ethernet is used, the IP and port must be set, e.g.: drvAsynIPPortConfigure("testRemote","10.5.1.181:22222",0,0,1) -Phytron (MCM) controller port is configured (and connected to previously created -asyn port) by running the following iocsh function: +Phytron (phyMOTION MCM) controller port is configured (and connected to +previously created asyn port) by running the following iocsh function: phytronCreateController(const char *phytronPortName, const char *asynPortName, - int movingPollPeriod, int idlePollPeriod, double timeout, int noResetAtBoot) -- phytronPortName: Name of the particular MCM unit. -- asynPortName: Name of the previously configured asyn port - interface to MCM + int movingPollPeriod, int idlePollPeriod, + double timeout, int noResetAtBoot) + +or Phytron (MCC) controller port is configured (and connected to previously +created asyn port) by running the following iocsh function: + +phytronCreateMCC(const char *phytronPortName, const char *asynPortName, + int address, int movingPollPeriod, int idlePollPeriod, + double timeout, int noResetAtBoot) + +- phytronPortName: Name of the particular MCM or MCC unit. +- asynPortName: Name of the previously configured asyn port (MCM/MCC interface) +- address (MCC only): hardware address switch value 0..15 - movingPollPeriod: The time between polls when any axis is moving in ms - idlePolPeriod: The time between polls when no axis is moving in ms - Timeout: Milliseconds before timeout for I/O requests - noResetAtBoot: if 1 then the controller is not reset at boot. If unset or 0 it is -where poll reads the basic axis status, e.g. position of the motor and of the -encoder, checks if axis is in movement, checks if motor is at the limit +where poll reads the basic axis status, e.g. position of the motor and of the +encoder, checks if axis is in movement, checks if motor is at the limit switch, ... Once the phytron controller is configured, user can initialize axes by running phytronCreateAxis(const char* phytronPortName, int module, int axis) -- phytronPortName: Previously defined name of the MCM unit -- module: index of the I1AM01 module connected to the MCM -- axis: index of the axis on the I1AM01 module +- phytronPortName: Previously defined name of the MCM/MCC unit +- module: index of the motor module connected to the MCM, MCC: use 0 +- axis: index of the axis on the motor module (starting with 1) Module index and axis index compose the axis asyn ADDR (ADDR macro) used in the motor.substitutions file. @@ -121,11 +142,11 @@ command phytronBrakeOutput could be used to configure this: phytronBrakeOutput(const char* phytronPortName, float fAxis, float fOutput, int bDisableMotor, double dEngageTime, double dReleaseTime) -- phytronPortName: Previously defined name of the MCM unit +- phytronPortName: Previously defined name of the MCM/MCC unit - fAxis: . as float number for axis selection - fOutput: . as float number for digital output selection or 0.0 to disable, negative value inverts output -- bDisableMotor: 0=keep motor enabled, 1=disable motor output, when idle +- bDisableMotor: 0=keep motor enabled, 1=disable motor output, when idle - dEngageTime: time is milliseconds to engage brake (max. 10 sec) and the motor is disabled after this time - dReleaseTime: time is milliseconds to release brake (max. 10 sec) @@ -137,9 +158,9 @@ Note: The brake support inside EPICS records may overwrite this. ******************************************************************************** WARNING: For every axis, the user must specify it's address (ADDR macro) in the -motor.substitutions file for Phytron_motor.db and PhytronI1AM01.db files. -The address is composed of the I1AM01 module index and the axis index. If, for -example, the startup script configures the following axis: +motor.substitutions file for the database files. The address is composed of the +I1AM01 module index and the axis index. If, for example, the startup script +configures the following axis: drvAsynIPPortConfigure("testRemote","10.5.1.181:22222",0,0,1) phytronCreateController ("phyMotionPort", "testRemote", 100, 100, 1000) @@ -172,26 +193,41 @@ phytronCreateController ("phyMotionPort", "testRemote", 100, 100, 1000) phytronCreateAxis("phyMotionPort", 1, 1) phytronCreateAxis("phyMotionPort", 2, 1) - cd $(MOTOR)/iocBoot/iocWithAsyn/ dbLoadTemplate "motor.substitutions.phytron" iocInit() + +========= Database: ========= -All three database files (Phytron_I1AM01.db, Phytron_MCM01.db, Phytron_motor.db) -in $(MOTOR_ROOT)/motorApp/Db are expanded by motor.substitutions.phytron file in -$(MOTOR_ROOT)/iocBoot/iocWithAsyn +All 8 database files (Phytron_AIO.db, Phytron_AIO_MCC.db, Phytron_CMD.db, +Phytron_DIO.db, Phytron_DIO_MCC1.db, Phytron_I1AM01.db, Phytron_MCM01.db, +Phytron_motor.db) in $(MOTOR_ROOT)/motorApp/Db are expanded by +motor.substitutions.phytron file in $(MOTOR_ROOT)/iocBoot/iocWithAsyn -******************************************************************************** -WARNING: For the controller to work properly all three database files ( -Phytron_I1AM01.db, Phytron_MCM01.db and Phytron_motor.db) must be used. -******************************************************************************** +For using an already configured motor axis, at least (Phytron_motor.db) must be +used. To configure an Phytron axis, also (Phytron_I1AM01.db) is necessary. +If you want to use phyMOTION controller functions, use (Phytron_MCM01.db). + +The analog I/O support needs the files (Phytron_AIO.db) for phyMOTION or +(Phytron_AIO_MCC.db) for MCC-1/MCC-2 controllers. The digital I/O support needs +the file (Phytron_DIO.db) and especially for MCC-1 include (Phytron_DIO_MCC1.db) +too. + +There is a direct communication option available with (Phytron_CMD.db). + + +Single motor axis features - Phytron_I1AM01.db or Phytron_MCC.db: +================================================================= + +Supported devices: +------------------ +The database files includes support for Phytron parameters used by different +axes. (Phytron_I1AM01.db) should support I1AM01, I1AM02, I1AM03, I1EM01, I1EM02. +(Phytron_MCC.db) should support MCC-1 and MCC-2. -=============================================== -I1AM01 Controller Features - Phytron_I1AM01.db: -=============================================== Initialization: --------------- Database contains 2 initialization records $(P)$(M)-INIT-AXIS-1/2 which are used @@ -201,7 +237,7 @@ motor.substitutions file. Status: ------- Database contains several status records. Ai record $(P)$(M)-STATUS_ reads the -status value from the I1AM01 module, 2 mbbiDirect records ($(P)$(M)-STATUS-1/2) +status value from the axis module, 2 mbbiDirect records ($(P)$(M)-STATUS-1/2) are provided to set the status bits - 23 bi records are provided to parse these status bits. An additional calc record is provided ($(P)$(M)-AXIS-ERR) which sets it's value to 1 if any of the error bits are set (if only notification @@ -212,7 +248,7 @@ Homing: RECORDS mbbo/mbbi: $(P)$(M)-HOMING_SET/_GET are used to set and readback the type of homing to be used. Homing is executed with the use of motor record's HOMF, HOMR fields. The following options are available for $(P)$(M)-HOMING_SET, -please keep in mind that offsets defined by I1AM01 parameters P11 and P12 (see +please keep in mind that offsets defined by Phytron parameters P11 and P12 (see phylogic-en.pdf and see below for records corresponding to P11 and P12) affect the final position after the homing procedure: <- recordValue (Homing description); Phytron commands for HOMF and HOMR> @@ -235,7 +271,7 @@ and start moving in the opposite direction until center switch is reached - Reference-Center (Driving on a reference signal to center) HOMF - m.aRC+, HOMR - m.aRC- -COMMEN: If limit switch is reached, controller goes to axis error state +COMMENT: If limit switch is reached, controller goes to axis error state - Ref-Center-Encoder (Driving on a reference signal to center and then to encoder zero pulse) @@ -273,8 +309,8 @@ process the initialization records $(P)$(M)-INIT-AXIS-1/2 after a DELAY time defined by the macro DLY in the substitutions file. -The following I1AM01 parameters are exposed as additional EPICS records: ------------------------------------------------------------------------- +The following parameters are exposed as additional EPICS records: +----------------------------------------------------------------- Param. index: feature; Record name(s): Comment: @@ -301,7 +337,7 @@ RECORDS ao/ai: $(P)$(M)-POS-TIMEOUT_SET/_GET P17: Defines when to use boost current RECORDS mbbo/mbbi: $(P)$(M)-BOOST_SET/_GET ------------------------------------- -P26: Encoder data transfer rate (ONLY FOR SSI) +P26: Encoder data transfer rate (phyMOTION and SSI only) RECORDS mbbo/mbbi: $(P)$(M)-ENC-RATE_SET/_GET ------------------------------------- P27: Limit switch type @@ -310,7 +346,7 @@ RECORDS mbbo/mbbi: $(P)$(M)-SWITCH-TYP_SET/_GET P28: Power stage off/on RECORDS bo/bi: $(P)$(M)-PWR-STAGE-MODE_SET/_GET ------------------------------------- -P34: Encoder type +P34: Encoder type (phyMOTION only) RECORDS mbbo/mbbi: $(P)$(M)-ENC-TYP_SET/_GET ------------------------------------- P35: Encoder resolution - for SSI and EnDat encoders @@ -348,53 +384,117 @@ RECORDS: ao/ai: $(P)$(M)-CURRENT-DELAY_SET/_GET P49: Power stage temperature RECORDS: ai: $(P)$(M)-PS-TEMPERATURE ------------------------------------- -P53: Power stage monitoring +P53: Power stage monitoring (phyMOTION only) RECORDS: bo/bi: $(P)$(M)-PS-MONITOR/_GET ------------------------------------- -P54: Motor temperature +P54: Motor temperature (phyMOTION only) RECORDS: ai: $(P)$(M)-MOTOR-TEMP +MCM01 Controller Features - Phytron_MCM01.db: +============================================= +This database file contains records for reading phyMOTION MCM01 status and to +reset the MCM01 module. -============================================ -MC01 Controller Features - Phytron_MCM01.db: -============================================ -This database file contains records for reading MCM01 status and to reset the -MCM01 module. - -Ai record $(P)-STATUS_ reads the status value from the MCM01 module, a -mbbiDirect record is provided to set the status bits - 16 bi records are -provided to parse these bits. An additional calc record is provided -($(P)-CON-ERR) which sets it's value to 1 if any of the error bits are set (if -only noticiation bits are set, e.g. terminal-activated, the value of +Ai record $(P)-STATUS_ reads the status value from the MCM01 module, a +mbbiDirect record is provided to set the status bits - 16 bi records are +provided to parse these bits. An additional calc record is provided +($(P)-CON-ERR) which sets it's value to 1 if any of the error bits are set (if +only noticiation bits are set, e.g. terminal-activated, the value of $(P)-CON-ERR is 0) -Bo record $(P)-RESET resets the MCM01 controller, every time it is processed. +Bo record $(P)-RESET resets the MCM01 controller, every time it is processed. $(P)-RESET_ alternates between 0 and 1, so the monitor is posted on every reset and the axis REINIT_ record can trigger the initialization procedures. -================================ + Motor Record - Phytron_motor.db: ================================ -This database file is similar to basic_asyn_motor.db, the only difference is, -that 2 additional fields are defined: - - field(ERES,"$(ERES)") - encoder resolution - - field(VMAX,"$(VMAX)") - maximum velocity +This database file is similar to (basic_asyn_motor.db), the differences are, +- additional field for autosave support + info(autosaveFields, "DVAL DIR VELO VBAS VMAX ACCL BDST BVEL BACC MRES ERES PREC EGU DHLM DLLM OFF") + +- additional required fields + field(ERES,"$(ERES)") - encoder resolution + field(MRES,"$(MRES)") - motor resolution (or field UREV) + field(UREV,"$(UREV)") - EGU's per revolution (or field MRES) + field(VMAX,"$(VMAX)") - maximum velocity + +- optional fields + field(DTYP,"$(DTYP=asynMotor)") - device type + field(MDEL,"$(MDEL=)") - monitor deadband + field(OFF, "$(OFF=0)") - offset + field(RDBD,"$(RDBD=0)") - retry dead band + field(RTRY,"$(RTRY=0)") - max. retry count + field(SREV,"$(SREV=0)") - steps per revolution + field(TWV, "$(TWV=1)") - tweak step value + field(UEIP,"$(UEIP=Yes)") - use encoder if Present + + +Supported analog and digital I/O - Phytron_{AIO,DIO}.db, +Phytron_{AIO_MCC,DIO_MCC1}.db: +======================================================== +The database files should be included for using extra I/O options. + +To use an analog or digital port, you have to create the controller first +(see above "phytronCreateController" or "phytronCreateMCC"). Then you have to +create a I/O instance for every analog port or digital group port with +"phytronCreateAnalog" or "phytronCreateDigital". These create additional +asyn ports and use the port of the controller. + +MCC-1 or MCC-2: + These devices has some 8 digital I/O and one or two analog inputs included. + Include the file (Phytron_AIO_MCC.db and/or Phytron_DIO.db) to use it. + Because the MCC-1 has bidirectional (shared) I/O ports, please include the + file (Phytron_DIO_MCC1.db) to configure the direction. + While creating a I/O port, use card number 1 and start with channel 1. + +phyMOTION: + This system needs the AIOM01.1 for analog and DIOM01.1 for digital I/O. + Any AIOM01.1 has 4 analog inputs and 4 analog outputs, please use the file + (Phytron_AIO.db). + Any DIOM01.1 has 8 digital inputs and 8 digital outputs, please use the file + (Phytron_DIO.db) + While creating a I/O port, you have to provide the card number and channel + (both starting with 1). The digital group has always the channel 1 + +While creating the I/O port, use could provide additional Phytron commands as +string, which will be send to configure the port or to set values. This command +list might be an empty string. + +The database files need an PORT, which is the newly created asyn port +(first argument to "phytronCreateAnalog" or "phytronCreateDigital"). +There is no need for any other address (ADDR) here (in opposite to axes). + + +Supported direct command interface - Phytron_CMD.db: +==================================================== +The database file should be included for direct command interface to the +Phytron controller (see above "phytronCreateController" or "phytronCreateMCC"). +It needs only a valid prefix P and the PORT of the controller. + +Usage: write a single command to the PV "$(P):directcommand" and look into +the resulting PVs "$(P):directreply" and "$(P):directstatus". The reply is +stored and the status has the value "ACK" on success (or execution) or "NAK", +if the controller rejected it. + + +List of remaining parameters handled by the motor record, internally by +controller, or not applicable +======================================================================= +P01 (Type of movement) - Set to hardware limit switches (1) -=============================================================================== -List of remaining I1AM01 parameters handled by the motor record, internally by -controller, or not applicable -=============================================================================== -P02 (Units of movemnt) - Always set to step - unit conversion is done within the -motor record. +P02 (Units of movement) - Always set to step - unit conversion is done within +the motor record. P03 (Conversion factor for the thread) - Always set to 1 - unit conversion is done within the motor record. P04 (Start/stop frequency) - Set by phytronAxis::move, before move is executed. -P07 (Emergency stop ramp) - Set by phytronAxis::stop, before stop is executed. +P07 (Emergency stop ramp) - Set to meaningful value, e.g. when hitting a limit +switch. Not set by this support. P08 (Initialization run frequency) - Set by phytronAxis::home, before homing is executed. @@ -404,11 +504,21 @@ P09 (Ramp M0P) - Set by phytronAxis::home, before homing is executed. P10 (Run frequency for leaving the limit switch) - Set by phytronAxis::home, before homing is executed +P11 (M0P offset for positive limit switch direction) - normally 0 + +P12 (M0P offset for negative limit switch direction) - normally 0 + +P13 (Recovery Time M0P) + P14 (Run frequency during program operation) - Set by phytronAxis::move, before move is executed P15 (Ramp for run frequency) - Set by phytronAxis::move, before move is executed +P16 (Recovery time position) - is ms (default 20) + +P17 (Boost) - 0=off (default), 1=always on, 2=while ac-/deceleration + P18 (Used internally by controller) P19 (Encoder deviation M0P counter) @@ -426,21 +536,60 @@ motor record handles software limits P25 (Compensation for play) - Ignored, because motor records handles backlash corrections +P26 (Link speed for SSI encoders) + +P27 (Limit switch type) - bit0: limit-, bit1: center, bit2: limit+, 0=NCC, 1=NOC + +P28 (axis options) Power stage mode after power on + P29 (Not used (by controller)) P30 and P31 (For I4XM01 only) P32 and P33 (Not used (by controller)) +P34 (Encoder type) - 0=none, 1=incremental 5.0V, ... + +P35 (Encoder resolution for SSI, EnDat encoders) + +P36 (Encoder function) - 0=disable, 1=enable SFI + +P37 (Encoder tolerance for SFI) + +P38 (Encoder Direction) - 0=positive, 1=negative + +P39 (Encoder Ratio) - set by phytronAxis::setEncoderRatio + +P40 (Stop current) - in 0.01A +P41 (Run current) - in 0.01A (see also P17) +P42 (Boost current) - in 0.01A (see also P17) + +P43 (Current hold time) + P44 (For I4XM01 only) +P45 (Step resolution) + P46, P47, P48 (Not used (by controller)) +P49 (Power stage temperature) in 0.1 deg C + P50 and P51 (For I4XM01 only) P52 (Internally used for trigger position) +P53 (Power stage monitor) + +P54 (Motor stage temperature) in 0.1 deg C +P55 (Motor temperature warning) +P56 (Motor temperature shut-off) + +P57 (Resolver voltage) + +P58 (Resolver ratio) + +==== GUI: ==== The follwoing CSS BOY GUI screens are located in motorApp/op/opi: @@ -484,7 +633,55 @@ Contains an LED for each of the 16 bi records parsing the $(P)-STATUS-BITS_ mbbiDirect record - - - - +====================== +Asyn option interface: +====================== +This support wants to be backward compatible to older versions as much as +possible. It supports setting additional options via the asyn option interface +with iocsh commands "asynSetOption" or "asynShowOption". ADDR=0 means the +controller and ADDR=1... means a specific axis, which was created using +"phytronCreateAxis" above: + +- asynSetOption(PORT, ADDR, "pollMethod", ...) + This option allows to speed up communication with MCM controllers. Is is + possible to set up different methods for different axes and the controller + (ADDR=0). These methods are available: + * "serial" or "no-parallel" or "not-parallel" or "old": this is the default + method. It is compatible but slow and does a handshake (request + reply) + for every command, + * "axis" or "axis-parallel": this does some parallel command execution (one + handshake for every axis) and is supported on many controllers, + * "parallel" or "controller-parallel": this tries to do one handshake for all + axes at a time. This is the fastest communication and supported on many + controllers, + * "default" or "standard": this is for a single axis only and uses the + setting for the controller. + Every handshake to a TCP-connected controller takes around 10ms, so the + serial method takes 40ms per axis, the axis-parallel method takes 10ms per + axis and the fastest controller-parallel method takes 10ms for _all_ axes. + Note: This option is not supported by the MCC controllers. +- asynShowOption(PORT, ADDR, "pollMethod") shows the actual value as numeric + value with text + +- asynSetOption(PORT, ADDR, "fakeHomedEnable", ...) + This option enables support for faked "homed" status of all axes. It uses + the Phytron controller registers 1001 to 1020, which are normally zeroed + after reset. The "homed" status is reset with any home command and will be + set, if the motor record sets a new position (e.g. AUTOSAVE). This is or-ed + together with the hardware "homed" status. The register 1001 will also be + used to detect restarted motor controllers. + Allowed values are "true" or "false" (default). +- asynShowOption(PORT, ADDR, "fakeHomedEnable") shows the actual value. + +- asynShowOption(PORT, ADDR, "fakeHomedCache") shows the actual value of the + software faked HOMED bits for all axes as list of decimal values. This is + a debugging helper. The value index is (ADDR/10-1) and every bit corresponds + to ADDR modulo 10 of the axes. Bit0 of 1st value is always set for reset + detection. + +- asynSetOption(PORT, ADDR, "allowExitOnError", ...) + This option allows to exit the IOC in case of serious errors, e.g. a + restarted motor controller. Normally an IOC should be started via procServ, + which will restart it. + Allowed values are "true" or "false" (default). +- asynShowOption(PORT, ADDR, "allowExitOnError") shows the actual value. diff --git a/phytronApp/src/phytronAxisMotor.cpp b/phytronApp/src/phytronAxisMotor.cpp index a96f48f..341d14b 100644 --- a/phytronApp/src/phytronAxisMotor.cpp +++ b/phytronApp/src/phytronAxisMotor.cpp @@ -1,11 +1,12 @@ /* +SPDX-License-Identifier: EPICS FILENAME... phytronAxisMotor.cpp USAGE... Motor driver support for Phytron Axis controller. Tom Slejko & Bor Marolt Cosylab d.d. 2014 -Lutz Rossa, Helmholtz-Zentrum Berlin fuer Materialien und Energy GmbH, 2021-2023 +Lutz Rossa, Helmholtz-Zentrum Berlin fuer Materialien und Energy GmbH, 2021-2025 */ @@ -22,16 +23,21 @@ Lutz Rossa, Helmholtz-Zentrum Berlin fuer Materialien und Energy GmbH, 2021-2023 #endif #include -#include #include #include #include +#include +#include +#include #include - #include #include "phytronAxisMotor.h" -#include +#include "phytronIOctrl.h" + +//****************************************************************************** +// DEFINES and GLOBAL VARIABLES +//****************************************************************************** using namespace std; @@ -41,27 +47,27 @@ using namespace std; #define CHECK_CTRL(xfunc, xtxt, xextra) \ if (phyStatus) { \ - if (phyStatus != lastStatus) { \ + if (phyStatus != lastStatus_) { \ asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, \ "phytronController::%s: (%s) %s failed with error code: %d, reason: %d\n", \ xfunc, this->controllerName_, xtxt, phyStatus, pasynUser->reason); \ - lastStatus = phyStatus; \ + lastStatus_ = phyStatus; \ } \ xextra; \ } \ - lastStatus = phyStatus + lastStatus_ = phyStatus #define CHECK_AXIS(xfunc, xtxt, xinstance, xextra) \ if (phyStatus) { \ - if (phyStatus != lastStatus) { \ + if (phyStatus != lastStatus_) { \ asynPrint(xinstance->pasynUser_, ASYN_TRACE_ERROR, \ "phytronAxis::%s: (%s) %s for axis %d failed with error code: %d\n", \ xfunc, xinstance->pC_->controllerName_, xtxt, xinstance->axisNo_, phyStatus); \ - lastStatus = phyStatus; \ + lastStatus_ = phyStatus; \ } \ xextra; \ } \ - lastStatus = phyStatus + lastStatus_ = phyStatus //Used for casting position doubles to integers #define NINT(f) (int)((f)>0 ? (f)+0.5 : (f)-0.5) @@ -69,37 +75,55 @@ using namespace std; //Specify maximum sleep time after brake was (de)activated in seconds #define MAXIMUM_BRAKE_TIME 10.0 +//static size of fixed array +#define ARRAY_SIZE(x) ((int)(sizeof(x) / sizeof((x)[0]))) + /* * Contains phytronController instances, phytronCreateAxis uses it to find and * bind axis object to the correct controller object. */ -static vector controllers; +std::vector phytronController::controllers_; + +//****************************************************************************** +// PHYTRON CONTROLLER IMPLEMENTATION +//****************************************************************************** /** Creates a new phytronController object. - * \param[in] portName The name of the asyn port that will be created for this driver - * \param[in] phytronPortName The name of the drvAsynIPPort that was created previously to connect to the phytron controller - * \param[in] movingPollPeriod The time between polls when any axis is moving - * \param[in] idlePollPeriod The time between polls when no axis is moving - * \param[in] noResetAtBoot if 0 or not set then controller is reset, if 1 then it's not - */ -phytronController::phytronController(const char *phytronPortName, const char *asynPortName, - double movingPollPeriod, double idlePollPeriod, double timeout, bool noResetAtBoot) - : asynMotorController(phytronPortName, + * \param[in] iCtrlType Controller type: phyMOTION or MCC type + * \param[in] phytronPortName The name of the drvAsynIPPort that was created previously to connect to the phytron controller + * \param[in] asynPortName The name of the asyn port that will be created for this driver + * \param[in] address Serial address of contoller 0..15 (hardware selector) + * \param[in] movingPollPeriod The time between polls when any axis is moving + * \param[in] idlePollPeriod The time between polls when no axis is moving + * \param[in] timeout Communication timeout in ms + * \param[in] noResetAtBoot if 0 or not set then controller is reset, if 1 then it's not + */ +phytronController::phytronController(phytronController::TYPE iCtrlType, const char* szPhytronPortName, const char* szAsynPortName, + int iAddress, double iMovingPollPeriod, double iIdlePollPeriod, double dTimeout, bool iNoResetAtBoot) + : asynMotorController(szPhytronPortName, 0xFF, NUM_PHYTRON_PARAMS, - asynOptionMask, // additional interfaces - 0, //No additional callback interfaces beyond those in base class + asynOctetMask | asynOptionMask, // additional interfaces + asynOctetMask, // additional callback interfaces ASYN_CANBLOCK | ASYN_MULTIDEVICE, 1, // autoconnect 0, 0)// Default priority and stack size + + , iCtrlType_(iCtrlType) + , iAddress_(iAddress) + , timeout_(dTimeout) + , lastStatus_(phytronSuccess) , do_initial_readout_(true) , iDefaultPollMethod_(pollMethodSerial) + , fake_homed_enable_(false) + , allow_exit_on_error_(false) + , statusResetTime_(0.0) { asynStatus status; static const char *functionName = "phytronController::phytronController"; - //Timeout is defined in milliseconds, but sendPhytronCommand expects seconds - timeout_ = timeout/1000; + memset(fake_homed_cache_, 0, sizeof(fake_homed_cache_)); + fake_homed_cache_[0] = 1; //pyhtronCreateAxis uses portName to identify the controller this->controllerName_ = (char *) mallocMustSucceed(sizeof(char)*(strlen(portName)+1), @@ -113,39 +137,44 @@ phytronController::phytronController(const char *phytronPortName, const char *as createParam(resetControllerString, asynParamInt32, &this->resetController_); //Create Axis parameters - createParam(axisStatusResetString, asynParamInt32, &this->axisStatusReset_); - createParam(axisResetString, asynParamInt32, &this->axisReset_); - createParam(axisStatusString, asynParamInt32, &this->axisStatus_); - createParam(homingProcedureString, asynParamInt32, &this->homingProcedure_); - createParam(axisModeString, asynParamInt32, &this->axisMode_); - createParam(mopOffsetPosString, asynParamInt32, &this->mopOffsetPos_); - createParam(mopOffsetNegString, asynParamInt32, &this->mopOffsetNeg_); - createParam(stepResolutionString, asynParamInt32, &this->stepResolution_); - createParam(stopCurrentString, asynParamInt32, &this->stopCurrent_); - createParam(runCurrentString, asynParamInt32, &this->runCurrent_); - createParam(boostCurrentString, asynParamInt32, &this->boostCurrent_); - createParam(encoderTypeString, asynParamInt32, &this->encoderType_); - createParam(initRecoveryTimeString, asynParamInt32, &this->initRecoveryTime_); - createParam(positionRecoveryTimeString, asynParamInt32, &this->positionRecoveryTime_); - createParam(boostConditionString, asynParamInt32, &this->boost_); - createParam(encoderRateString, asynParamInt32, &this->encoderRate_); - createParam(switchTypString, asynParamInt32, &this->switchTyp_); - createParam(pwrStageModeString, asynParamInt32, &this->pwrStageMode_); - createParam(encoderResolutionString, asynParamInt32, &this->encoderRes_); - createParam(encoderFunctionString, asynParamInt32, &this->encoderFunc_); - createParam(encoderSFIWidthString, asynParamInt32, &this->encoderSFIWidth_); - createParam(encoderDirectionString, asynParamInt32, &this->encoderDirection_); - createParam(powerStagetMonitorString, asynParamInt32, &this->powerStageMonitor_); - createParam(currentDelayTimeString, asynParamInt32, &this->currentDelayTime_); + createParam(axisStatusResetString, asynParamInt32, &this->axisStatusReset_); + createParam(axisResetString, asynParamInt32, &this->axisReset_); + createParam(axisStatusString, asynParamInt32, &this->axisStatus_); + createParam(homingProcedureString, asynParamInt32, &this->homingProcedure_); + createParam(axisModeString, asynParamInt32, &this->axisMode_); + createParam(mopOffsetPosString, asynParamInt32, &this->mopOffsetPos_); + createParam(mopOffsetNegString, asynParamInt32, &this->mopOffsetNeg_); + createParam(stepResolutionString, asynParamInt32, &this->stepResolution_); + createParam(stopCurrentString, asynParamInt32, &this->stopCurrent_); + createParam(runCurrentString, asynParamInt32, &this->runCurrent_); + createParam(boostCurrentString, asynParamInt32, &this->boostCurrent_); + createParam(encoderTypeString, asynParamInt32, &this->encoderType_); + createParam(initRecoveryTimeString, asynParamInt32, &this->initRecoveryTime_); + createParam(positionRecoveryTimeString, asynParamInt32, &this->positionRecoveryTime_); + createParam(boostConditionString, asynParamInt32, &this->boost_); + createParam(encoderRateString, asynParamInt32, &this->encoderRate_); + createParam(switchTypString, asynParamInt32, &this->switchTyp_); + createParam(pwrStageModeString, asynParamInt32, &this->pwrStageMode_); + createParam(encoderResolutionString, asynParamInt32, &this->encoderRes_); + createParam(encoderFunctionString, asynParamInt32, &this->encoderFunc_); + createParam(encoderSFIWidthString, asynParamInt32, &this->encoderSFIWidth_); + createParam(encoderDirectionString, asynParamInt32, &this->encoderDirection_); + createParam(powerStagetMonitorString, asynParamInt32, &this->powerStageMonitor_); + createParam(currentDelayTimeString, asynParamInt32, &this->currentDelayTime_); createParam(powerStageTempString, asynParamFloat64, &this->powerStageTemp_); createParam(motorTempString, asynParamFloat64, &this->motorTemp_); createParam(axisBrakeOutputString, asynParamFloat64, &this->axisBrakeOutput_); - createParam(axisDisableMotorString, asynParamInt32, &this->axisDisableMotor_); + createParam(axisDisableMotorString, asynParamInt32, &this->axisDisableMotor_); createParam(axisBrakeEngageTimeString, asynParamFloat64, &this->axisBrakeEngageTime_); createParam(axisBrakeReleaseTimeString, asynParamFloat64, &this->axisBrakeReleaseTime_); + createParam(directCommandString, asynParamOctet, &this->directCommand_); + createParam(directReplyString, asynParamOctet, &this->directReply_); + createParam(directStatusString, asynParamInt32, &this->directStatus_); + setStringParam(0, directReply_, ""); + setIntegerParam(0, directStatus_, 0); /* Connect to phytron controller */ - status = pasynOctetSyncIO->connect(asynPortName, 0, &pasynUserController_, NULL); + status = pasynOctetSyncIO->connect(szAsynPortName, 0, &pasynUserController_, NULL); if (status) { asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, "%s: cannot connect to phytron controller\n", @@ -154,48 +183,106 @@ phytronController::phytronController(const char *phytronPortName, const char *as // this hook forces an initial readout before the motor record has // finished initializing (before pass 1), so it has actual values for // REP, RMP, MSTA fields, especially for absolute encoders - if (controllers.empty()) + if (controllers_.empty()) initHookRegister(&phytronController::epicsInithookFunction); //phytronCreateAxis will search for the controller for axis registration - controllers.push_back(this); + controllers_.push_back(this); - if (noResetAtBoot != 1){ + if (iNoResetAtBoot != 1) { + epicsTimeStamp tStart, tNow; //RESET THE CONTROLLER if (sendPhytronCommand(std::string("CR"))) asynPrint(this->pasynUserSelf, ASYN_TRACE_WARNING, "phytronController::phytronController: Could not reset controller %s\n", this->controllerName_); //Wait for reset to finish - epicsThreadSleep(10.0); - } + epicsThreadSleep(5.0); + epicsTimeGetCurrent(&tStart); + while (sendPhytronCommand(std::string("S")) != phytronSuccess) + { + epicsTimeGetCurrent(&tNow); + if (epicsTimeDiffInSeconds(&tNow, &tStart) >= 120.) + { + asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, + "phytronController::phytronController: no valid answer after controller reset %s\n", this->controllerName_); + break; + } + epicsThreadSleep(0.5); + } + } + if (iCtrlType == TYPE_MCC) + { + std::string sTmp; + sendPhytronCommand(std::string("IAR"), sCtrlType_); + sCtrlType_.insert(0, "MCC-"); + if (sendPhytronCommand(std::string("1P48R"), sTmp) == phytronSuccess) + { + long lType(-1); + if (epicsParseLong(sTmp.c_str(), &lType, 0, NULL) != 0) + lType = -1; + switch (lType) + { + case 0: break; // MCC-1/MCC-2 chopper + case 1: sCtrlType_.append(" LIN"); break; // MCC-1 linear + default: + sCtrlType_.append(" type"); + sCtrlType_.append(sTmp); + break; + } + } + } + else + sendPhytronCommand(std::string("IM0"), sCtrlType_); + sCtrlType_ = escapeC(sCtrlType_); - startPoller(movingPollPeriod, idlePollPeriod, 5); + startPoller(iMovingPollPeriod, iIdlePollPeriod, 5); } } -/** Creates a new phytronController object. +/** Creates a new phytronController object for a Phytron phyMOTION. + * Configuration command, called directly or from iocsh + * \param[in] szPhytronPortName The name of the asyn port that will be created for this driver + * \param[in] szAsynPortName The name of the drvAsynIPPPort that was created previously to connect to the phytron controller + * \param[in] iMovingPollPeriod The time in ms between polls when any axis is moving + * \param[in] iIdlePollPeriod The time in ms between polls when no axis is moving + * \param[in] dTimeout Communication timeout in ms + * \param[in] iNoResetAtBoot if 0 or not set then controller is reset, if 1 then it's not + */ +int phytronController::phytronCreatePhymotion(const char* szPhytronPortName, + const char* szAsynPortName, int iMovingPollPeriod, int iIdlePollPeriod, + double dTimeout, int iNoResetAtBoot) +{ + new phytronController(phytronController::TYPE_PHYMOTION, szPhytronPortName, szAsynPortName, 0, + iMovingPollPeriod/1000., iIdlePollPeriod/1000., dTimeout/1000., iNoResetAtBoot); + return asynSuccess; +} + +/** Creates a new phytronController object for a Phytron MCC-1 or MCC-2. * Configuration command, called directly or from iocsh - * \param[in] portName The name of the asyn port that will be created for this driver - * \param[in] phytronPortName The name of the drvAsynIPPPort that was created previously to connect to the phytron controller - * \param[in] numController number of axes that this controller supports is numController*AXES_PER_CONTROLLER - * \param[in] movingPollPeriod The time in ms between polls when any axis is moving - * \param[in] idlePollPeriod The time in ms between polls when no axis is moving - * \param[in] noResetAtBoot if 0 or not set then controller is reset, if 1 then it's not + * \param[in] szPhytronPortName The name of the asyn port that will be created for this driver + * \param[in] szAsynPortName The name of the drvAsynIPPPort that was created previously to connect to the phytron controller + * \param[in] iAddress Serial address of contoller 0..15 (hardware selector) + * \param[in] iMovingPollPeriod The time in ms between polls when any axis is moving + * \param[in] iIdlePollPeriod The time in ms between polls when no axis is moving + * \param[in] dTimeout Communication timeout in ms + * \param[in] iNoResetAtBoot if 0 or not set then controller is reset, if 1 then it's not */ -extern "C" int phytronCreateController(const char *phytronPortName, const char *asynPortName, - int movingPollPeriod, int idlePollPeriod, double timeout, int noResetAtBoot) +int phytronController::phytronCreateMCC(const char* szPhytronPortName, + const char* szAsynPortName, int iAddress, int iMovingPollPeriod, + int iIdlePollPeriod, double dTimeout, int iNoResetAtBoot) { - new phytronController(phytronPortName, asynPortName, movingPollPeriod/1000., idlePollPeriod/1000., timeout,noResetAtBoot); + new phytronController(phytronController::TYPE_MCC, szPhytronPortName, szAsynPortName, iAddress, + iMovingPollPeriod/1000., iIdlePollPeriod/1000., dTimeout/1000., iNoResetAtBoot); return asynSuccess; } /** asynUsers use this to read integer parameters - * \param[in] pasynUser asynUser structure containing the reason - * \param[out] value Parameter value + * \param[in] pasynUser asynUser structure containing the reason + * \param[out] piValue Parameter value */ -asynStatus phytronController::readInt32(asynUser *pasynUser, epicsInt32 *value) +asynStatus phytronController::readInt32(asynUser* pasynUser, epicsInt32* piValue) { phytronAxis *pAxis; phytronStatus phyStatus; @@ -203,7 +290,7 @@ asynStatus phytronController::readInt32(asynUser *pasynUser, epicsInt32 *value) int iParameter(0); //Call base implementation first - asynMotorController::readInt32(pasynUser, value); + asynMotorController::readInt32(pasynUser, piValue); //Check if this is a call to read a controller parameter if(pasynUser->reason == resetController_ || pasynUser->reason == controllerStatusReset_){ @@ -213,7 +300,7 @@ asynStatus phytronController::readInt32(asynUser *pasynUser, epicsInt32 *value) phyStatus = sendPhytronCommand(std::string("ST"), sResponse); CHECK_CTRL("readInt32", "Reading controller status", return phyToAsyn(phyStatus)); - *value = atoi(sResponse.c_str()); + *piValue = atoi(sResponse.c_str()); return asynSuccess; } @@ -226,13 +313,13 @@ asynStatus phytronController::readInt32(asynUser *pasynUser, epicsInt32 *value) } if(pasynUser->reason == homingProcedure_){ - getIntegerParam(pAxis->axisNo_, homingProcedure_, value); + getIntegerParam(pAxis->axisNo_, homingProcedure_, piValue); return asynSuccess; } else if (pasynUser->reason == axisReset_ || pasynUser->reason == axisStatusReset_){ //Called only on initialization of AXIS-RESET and AXIS-STATUS-RESET bo records return asynSuccess; } else if (pasynUser->reason == axisDisableMotor_) { - *value = pAxis->disableMotor_ & 1; + *piValue = pAxis->disableMotor_ & 1; return asynSuccess; } else if (pasynUser->reason == axisMode_) iParameter = 1; @@ -242,7 +329,7 @@ asynStatus phytronController::readInt32(asynUser *pasynUser, epicsInt32 *value) else if (pasynUser->reason == stopCurrent_) iParameter = 40; else if (pasynUser->reason == runCurrent_) iParameter = 41; else if (pasynUser->reason == boostCurrent_) iParameter = 42; - else if (pasynUser->reason == encoderType_) iParameter = 43; + else if (pasynUser->reason == encoderType_) iParameter = 34; else if (pasynUser->reason == initRecoveryTime_) iParameter = 13; else if (pasynUser->reason == positionRecoveryTime_) iParameter = 16; else if (pasynUser->reason == boost_) iParameter = 17; @@ -257,31 +344,32 @@ asynStatus phytronController::readInt32(asynUser *pasynUser, epicsInt32 *value) else if (pasynUser->reason == powerStageMonitor_) iParameter = 53; else return asynSuccess; // ignore unhandled request - phyStatus = sendPhytronCommand(std::string("M") + pAxis->axisModuleNo_ + "P" + std::to_string(iParameter) + "R", + phyStatus = sendPhytronCommand(std::string(pAxis->axisModuleNo_) + "P" + std::to_string(iParameter) + "R", sResponse); CHECK_AXIS("readInt32", "Reading parameter", pAxis, return phyToAsyn(phyStatus)); - *value = atoi(sResponse.c_str()); + *piValue = atoi(sResponse.c_str()); //{STOP,RUN,BOOST} current records have EGU set to mA, but device returns 10mA if(pasynUser->reason == stopCurrent_ || pasynUser->reason == runCurrent_ || pasynUser->reason == boostCurrent_) - *value *= 10; + *piValue *= 10; return asynSuccess; } /** asynUsers use this to write integer parameters - * \param[in] pasynUser asynUser structure containing the reason - * \param[in] value Parameter value to be written + * \param[in] pasynUser asynUser structure containing the reason + * \param[in] iValue Parameter value to be written */ -asynStatus phytronController::writeInt32(asynUser *pasynUser, epicsInt32 value) +asynStatus phytronController::writeInt32(asynUser* pasynUser, epicsInt32 iValue) { phytronAxis *pAxis; phytronStatus phyStatus(phytronSuccess); //Call base implementation first - asynMotorController::writeInt32(pasynUser, value); + asynMotorController::writeInt32(pasynUser, iValue); + const char* szSetChar((iCtrlType_ == TYPE_PHYMOTION) ? "=" : "S"); /* * Check if this is a call to reset the controller, else it is an axis request @@ -308,61 +396,63 @@ asynStatus phytronController::writeInt32(asynUser *pasynUser, epicsInt32 value) this->outString_[0] = '\0'; if(pasynUser->reason == homingProcedure_){ - setIntegerParam(pAxis->axisNo_, pasynUser->reason, value); + setIntegerParam(pAxis->axisNo_, pasynUser->reason, iValue); callParamCallbacks(); return asynSuccess; } else if(pasynUser->reason == axisReset_){ - sprintf(this->outString_, "M%sC", pAxis->axisModuleNo_); + sprintf(this->outString_, "%sC", pAxis->axisModuleNo_); } else if(pasynUser->reason == axisStatusReset_){ - sprintf(this->outString_, "SEC%s", pAxis->axisModuleNo_); + if (iCtrlType_ != TYPE_PHYMOTION) + return asynError; + sprintf(this->outString_, "SEC%s", &pAxis->axisModuleNo_[1]); } else if(pasynUser->reason == axisMode_){ - sprintf(this->outString_, "M%sP01=%d", pAxis->axisModuleNo_,value); + sprintf(this->outString_, "%sP01%s%d", pAxis->axisModuleNo_, szSetChar,iValue); } else if(pasynUser->reason == mopOffsetPos_){ - sprintf(this->outString_, "M%sP11=%d", pAxis->axisModuleNo_,value); + sprintf(this->outString_, "%sP11%s%d", pAxis->axisModuleNo_, szSetChar,iValue); } else if(pasynUser->reason == mopOffsetNeg_){ - sprintf(this->outString_, "M%sP12=%d", pAxis->axisModuleNo_,value); + sprintf(this->outString_, "%sP12%s%d", pAxis->axisModuleNo_, szSetChar,iValue); } else if (pasynUser->reason == stepResolution_){ - sprintf(this->outString_, "M%sP45=%d", pAxis->axisModuleNo_,value); + sprintf(this->outString_, "%sP45%s%d", pAxis->axisModuleNo_, szSetChar,iValue); } else if (pasynUser->reason == stopCurrent_){ - value /= 10; //STOP_CURRENT record has EGU mA, device expects 10mA - sprintf(this->outString_, "M%sP40=%d", pAxis->axisModuleNo_,value); + iValue /= 10; //STOP_CURRENT record has EGU mA, device expects 10mA + sprintf(this->outString_, "%sP40%s%d", pAxis->axisModuleNo_, szSetChar,iValue); } else if (pasynUser->reason == runCurrent_){ - value /= 10; //RUN_CURRENT record has EGU mA, device expects 10mA - sprintf(this->outString_, "M%sP41=%d", pAxis->axisModuleNo_,value); + iValue /= 10; //RUN_CURRENT record has EGU mA, device expects 10mA + sprintf(this->outString_, "%sP41%s%d", pAxis->axisModuleNo_, szSetChar,iValue); } else if (pasynUser->reason == boostCurrent_){ - value /= 10; //BOOST_CURRENT record has EGU mA, device expects 10mA - sprintf(this->outString_, "M%sP42=%d", pAxis->axisModuleNo_,value); + iValue /= 10; //BOOST_CURRENT record has EGU mA, device expects 10mA + sprintf(this->outString_, "%sP42%s%d", pAxis->axisModuleNo_, szSetChar,iValue); } else if (pasynUser->reason == encoderType_){ - sprintf(this->outString_, "M%sP34=%d", pAxis->axisModuleNo_, value); + sprintf(this->outString_, "%sP34%s%d", pAxis->axisModuleNo_, szSetChar, iValue); } else if (pasynUser->reason == initRecoveryTime_){ - sprintf(this->outString_, "M%sP13=%d", pAxis->axisModuleNo_, value); + sprintf(this->outString_, "%sP13%s%d", pAxis->axisModuleNo_, szSetChar, iValue); } else if (pasynUser->reason == positionRecoveryTime_){ - sprintf(this->outString_, "M%sP16=%d", pAxis->axisModuleNo_, value); + sprintf(this->outString_, "%sP16%s%d", pAxis->axisModuleNo_, szSetChar, iValue); } else if (pasynUser->reason == boost_){ - sprintf(this->outString_, "M%sP17=%d", pAxis->axisModuleNo_, value); + sprintf(this->outString_, "%sP17%s%d", pAxis->axisModuleNo_, szSetChar, iValue); } else if (pasynUser->reason == encoderRate_){ - sprintf(this->outString_, "M%sP26=%d", pAxis->axisModuleNo_, value); + sprintf(this->outString_, "%sP26%s%d", pAxis->axisModuleNo_, szSetChar, iValue); } else if (pasynUser->reason == switchTyp_){ - sprintf(this->outString_, "M%sP27=%d", pAxis->axisModuleNo_, value); + sprintf(this->outString_, "%sP27%s%d", pAxis->axisModuleNo_, szSetChar, iValue); } else if (pasynUser->reason == pwrStageMode_){ - sprintf(this->outString_, "M%sP28=%d", pAxis->axisModuleNo_, value); + sprintf(this->outString_, "%sP28%s%d", pAxis->axisModuleNo_, szSetChar, iValue); } else if (pasynUser->reason == encoderRes_){ - sprintf(this->outString_, "M%sP35=%d", pAxis->axisModuleNo_, value); + sprintf(this->outString_, "%sP35%s%d", pAxis->axisModuleNo_, szSetChar, iValue); } else if (pasynUser->reason == encoderFunc_){ //Value is VAL field of parameter P37 record. If P37 is positive P36 is set to 1, else 0 - sprintf(this->outString_, "M%sP36=%d", pAxis->axisModuleNo_, value > 0 ? 1 : 0); + sprintf(this->outString_, "%sP36%s%d", pAxis->axisModuleNo_, szSetChar, iValue > 0 ? 1 : 0); } else if(pasynUser->reason == encoderSFIWidth_){ - sprintf(this->outString_, "M%sP37=%d", pAxis->axisModuleNo_, value); + sprintf(this->outString_, "%sP37%s%d", pAxis->axisModuleNo_, szSetChar, iValue); } else if(pasynUser->reason == encoderSFIWidth_){ - sprintf(this->outString_, "M%sP38=%d", pAxis->axisModuleNo_, value); + sprintf(this->outString_, "%sP38%s%d", pAxis->axisModuleNo_, szSetChar, iValue); } else if(pasynUser->reason == powerStageMonitor_){ - sprintf(this->outString_, "M%sP53=%d", pAxis->axisModuleNo_, value); + sprintf(this->outString_, "%sP53%s%d", pAxis->axisModuleNo_, szSetChar, iValue); } else if(pasynUser->reason == currentDelayTime_){ - sprintf(this->outString_, "M%sP43=%d", pAxis->axisModuleNo_, value); + sprintf(this->outString_, "%sP43%s%d", pAxis->axisModuleNo_, szSetChar, iValue); } else if(pasynUser->reason == encoderDirection_){ - sprintf(this->outString_, "M%sP38=%d", pAxis->axisModuleNo_, value); + sprintf(this->outString_, "%sP38%s%d", pAxis->axisModuleNo_, szSetChar, iValue); } else if(pasynUser->reason == axisDisableMotor_){ - pAxis->disableMotor_ = (pAxis->disableMotor_ & (~1)) | (value != 0) ? 1 : 0; + pAxis->disableMotor_ = (pAxis->disableMotor_ & (~1)) | ((iValue != 0) ? 1 : 0); pAxis->setBrakeOutput(NULL, pAxis->brakeReleased_); return asynSuccess; } @@ -376,10 +466,10 @@ asynStatus phytronController::writeInt32(asynUser *pasynUser, epicsInt32 value) } /** asynUsers use this to read float parameters - * \param[in] pasynUser asynUser structure containing the reason - * \param[out] value Parameter value + * \param[in] pasynUser asynUser structure containing the reason + * \param[out] pdValue Parameter value */ -asynStatus phytronController::readFloat64(asynUser *pasynUser, epicsFloat64 *value) +asynStatus phytronController::readFloat64(asynUser *pasynUser, epicsFloat64 *pdValue) { phytronAxis *pAxis; phytronStatus phyStatus(phytronSuccess); @@ -393,21 +483,21 @@ asynStatus phytronController::readFloat64(asynUser *pasynUser, epicsFloat64 *val } //Call base implementation first - asynMotorController::readFloat64(pasynUser, value); + asynMotorController::readFloat64(pasynUser, pdValue); this->outString_[0] = '\0'; if(pasynUser->reason == powerStageTemp_){ - sprintf(this->outString_, "M%sP49R", pAxis->axisModuleNo_); + sprintf(this->outString_, "%sP49R", pAxis->axisModuleNo_); } else if(pasynUser->reason == motorTemp_){ - sprintf(this->outString_, "M%sP54R", pAxis->axisModuleNo_); + sprintf(this->outString_, "%sP54R", pAxis->axisModuleNo_); } else if(pasynUser->reason == axisBrakeOutput_){ - *value = pAxis->brakeOutput_; + *pdValue = pAxis->brakeOutput_; return asynSuccess; } else if(pasynUser->reason == axisBrakeEngageTime_){ - *value = pAxis->brakeEngageTime_; + *pdValue = pAxis->brakeEngageTime_; return asynSuccess; } else if(pasynUser->reason == axisBrakeReleaseTime_){ - *value = pAxis->brakeReleaseTime_; + *pdValue = pAxis->brakeReleaseTime_; return asynSuccess; } @@ -416,17 +506,17 @@ asynStatus phytronController::readFloat64(asynUser *pasynUser, epicsFloat64 *val CHECK_AXIS("readFloat64", "Reading parameter", pAxis, return phyToAsyn(phyStatus)); //Power stage and motor temperature records have EGU °C, but device returns 0.1 °C - *value = atof(sResponse.c_str()) / 10.; + *pdValue = atof(sResponse.c_str()) / 10.; } return asynSuccess; } /** asynUsers use this to write float parameters - * \param[in] pasynUser asynUser structure containing the reason - * \param[in] value Parameter value to be written + * \param[in] pasynUser asynUser structure containing the reason + * \param[in] dValue Parameter value to be written */ -asynStatus phytronController::writeFloat64(asynUser *pasynUser, epicsFloat64 value) +asynStatus phytronController::writeFloat64(asynUser* pasynUser, epicsFloat64 dValue) { phytronAxis* pAxis(getAxis(pasynUser)); if(!pAxis){ @@ -436,35 +526,72 @@ asynStatus phytronController::writeFloat64(asynUser *pasynUser, epicsFloat64 val } if (pasynUser->reason == axisBrakeOutput_) - pAxis->brakeOutput_ = floor(10.0 * ((value > -100.0 && value < 100.) ? value : 0.0) + 0.5) / 10.0; + pAxis->brakeOutput_ = floor(10.0 * ((dValue > -100.0 && dValue < 100.) ? dValue : 0.0) + 0.5) / 10.0; else if (pasynUser->reason == axisBrakeEngageTime_) { - if (value > MAXIMUM_BRAKE_TIME) - value = MAXIMUM_BRAKE_TIME; - pAxis->brakeEngageTime_ = value > 0.0 ? value : 0.0; + if (dValue > MAXIMUM_BRAKE_TIME) + dValue = MAXIMUM_BRAKE_TIME; + pAxis->brakeEngageTime_ = dValue > 0.0 ? dValue : 0.0; } else if (pasynUser->reason == axisBrakeReleaseTime_) { - if (value > MAXIMUM_BRAKE_TIME) - value = MAXIMUM_BRAKE_TIME; - pAxis->brakeReleaseTime_ = value > 0.0 ? value : 0.0; + if (dValue > MAXIMUM_BRAKE_TIME) + dValue = MAXIMUM_BRAKE_TIME; + pAxis->brakeReleaseTime_ = dValue > 0.0 ? dValue : 0.0; } //Call base implementation - return asynMotorController::writeFloat64(pasynUser, value); + return asynMotorController::writeFloat64(pasynUser, dValue); +} + +/** Called when asyn clients call pasynOctet->write(). + * \param[in] pasynUser asynUser structure containing the reason + * \param[in] szValue string to write + * \param[in] maxChars number of characters to write + * \param[out] pnActual number of characters actually written + */ +asynStatus phytronController::writeOctet(asynUser* pasynUser, const char* szValue, size_t maxChars, size_t* pnActual) +{ + asynStatus iResult(asynSuccess); + std::string sCmd(szValue, maxChars), sReply; + if (pasynUser->reason == directCommand_) + { + iResult = phyToAsyn(sendPhytronCommand(sCmd, sReply, false)); + if (iResult == asynSuccess) + { + setStringParam(0, directReply_, ""); + setIntegerParam(0, directStatus_, 0); + if (sReply.substr(0, 1) == "\x06") setIntegerParam(0, directStatus_, 1); + else if (sReply.substr(0, 1) == "\x15") setIntegerParam(0, directStatus_, 2); + else + { + setIntegerParam(0, directStatus_, 3); + goto noerase; + } + sReply.erase(0, 1); +noerase: + setStringParam(0, directReply_, sReply); + } + } + else if (pasynUser->reason == directReply_ || pasynUser->reason == directStatus_) + iResult = asynError; + if (iResult == asynSuccess) + iResult = asynPortDriver::writeOctet(pasynUser, szValue, maxChars, pnActual); + return iResult; } /** Called when asyn clients call pasynOption->read(). * The base class implementation simply prints an error message. * Derived classes may reimplement this function if required. - * \param[in] pasynUser pasynUser structure that encodes the reason and address. - * \param[in] key Option key string. - * \param[out] value string to be returned - * \param[in] maxChars Size of value string + * \param[in] pasynUser pasynUser structure that encodes the reason and address. + * \param[in] szKey Option key string. + * \param[out] szValue string to be returned + * \param[in] iMaxChars Size of value string * \return asyn status */ -asynStatus phytronController::readOption(asynUser *pasynUser, const char *key, char *value, int maxChars) +asynStatus phytronController::readOption(asynUser* pasynUser, const char* szKey, char* szValue, int iMaxChars) { - if (key && value) { - *value = '\0'; - if (epicsStrCaseCmp(key, "pollMethod") == 0) { + if (szKey && szValue) { + *szValue = '\0'; + if (epicsStrCaseCmp(szKey, "pollMethod") == 0) { + // polling methods: serial, axis-parallel or controller-parallel; axis may have "default" const char* szMethod; phytronAxis* pAxis(getAxis(pasynUser)); enum pollMethod iMethod(pAxis ? pAxis->iPollMethod_ : iDefaultPollMethod_); @@ -475,22 +602,63 @@ asynStatus phytronController::readOption(asynUser *pasynUser, const char *key, c case pollMethodControllerParallel: szMethod = "ctrl-parallel"; break; default: szMethod = "???"; break; } - snprintf(value, maxChars, "%d/%s", static_cast(iMethod), szMethod); + snprintf(szValue, iMaxChars, "%d/%s", static_cast(iMethod), szMethod); + return asynSuccess; + } + + if (epicsStrCaseCmp(szKey, "clearAxisStatus") == 0) { + // "clear axis status" before move + phytronAxis* pAxis(getAxis(pasynUser)); + float fTime(fabs(pAxis ? pAxis->statusResetTime_ : statusResetTime_)); + if (!isfinite(fTime)) + snprintf(szValue, iMaxChars, "default"); + else if (fTime >= 0.001) + snprintf(szValue, iMaxChars, "%g", fTime); + else if (fTime > 0) + snprintf(szValue, iMaxChars, "on"); + else + snprintf(szValue, iMaxChars, "off"); + return asynSuccess; + } + + if (epicsStrCaseCmp(szKey, "fakeHomedEnable") == 0) { + // configure "fake homed" bit + snprintf(szValue, iMaxChars, "%s", fake_homed_enable_ ? "true" : "false"); + return asynSuccess; + } + + if (epicsStrCaseCmp(szKey, "fakeHomedCache") == 0) { + // show "fake homed" cache + int iLen(0); + szValue[0] = '\0'; + for (int i = 0; i < ARRAY_SIZE(fake_homed_cache_); ++i) { + snprintf(&szValue[iLen], iMaxChars - iLen, "%s%u", i ? "," : "", fake_homed_cache_[i]); + szValue[iMaxChars - 1] = '\0'; + iLen += strlen(&szValue[iLen]); + if ((iLen + 1) >= iMaxChars) + break; + } + return asynSuccess; + } + + if (epicsStrCaseCmp(szKey, "allowExitOnError") == 0) { + // configure "allow exit on error": after detection of this, the IOC exists and should be restarted by procServ + snprintf(szValue, iMaxChars, "%s", allow_exit_on_error_ ? "true" : "false"); return asynSuccess; } } - return asynMotorController::readOption(pasynUser, key, value, maxChars); + return asynMotorController::readOption(pasynUser, szKey, szValue, iMaxChars); } /** Called when asyn clients call pasynOption->write(). * The base class implementation simply prints an error message. * Derived classes may reimplement this function if required. - * \param[in] pasynUser pasynUser structure that encodes the reason and address. - * \param[in] key Option key string. - * \param[in] value Value string. + * \param[in] pasynUser pasynUser structure that encodes the reason and address. + * \param[in] szKey Option key string. + * \param[in] szValue Value string. * \return asyn status */ -asynStatus phytronController::writeOption(asynUser *pasynUser, const char *key, const char *value) +asynStatus phytronController::writeOption(asynUser* pasynUser, const char* szKey, const char* szValue) { phytronAxis* pAxis(NULL); enum pollMethod iMethod(pollMethodSerial); @@ -498,76 +666,166 @@ asynStatus phytronController::writeOption(asynUser *pasynUser, const char *key, int iAxisNo(0); size_t iLen; - if (!key || !value) - goto finish; - if (epicsStrCaseCmp(key, "pollMethod") != 0) + if (!szKey || !szValue) goto finish; - - getAddress(pasynUser, &iAxisNo); - if (iAxisNo > 0) { - pAxis = getAxis(iAxisNo); - if (!pAxis) { + if (epicsStrCaseCmp(szKey, "pollMethod") == 0) { + // polling methods: serial, axis-parallel or controller-parallel; axis may have "default" + getAddress(pasynUser, &iAxisNo); + if (iAxisNo > 0) { + pAxis = getAxis(iAxisNo); + if (!pAxis) { + epicsSnprintf(pasynUser->errorMessage, pasynUser->errorMessageSize, + "phytronController:writeOption(%s, %s) wrong axis", szKey, szValue); + return asynError; + } + iMethod = pollMethodDefault; + } + if (epicsParseInt64(szValue, &iTmp, 0, NULL) == 0) { + if (iTmp < (pAxis ? static_cast(pollMethodDefault) : static_cast(pollMethodSerial)) || + iTmp > static_cast(pollMethodControllerParallel)) + goto wrongValue_parallel; + iMethod = static_cast(iTmp); + } else { + while (isspace(*szValue)) ++szValue; + iLen = strlen(szValue); + while (iLen > 0 && isspace(szValue[iLen - 1])) --iLen; + + if (pAxis && ((iLen == 7 && !epicsStrnCaseCmp(szValue, "default", 7)) || + (iLen == 8 && !epicsStrnCaseCmp(szValue, "standard", 8)) || + (iLen == 10 && !epicsStrnCaseCmp(szValue, "controller", 10)))) + iMethod = pollMethodDefault; + else if ((iLen == 6 && !epicsStrnCaseCmp(szValue, "serial", 6)) || + (iLen == 11 && !epicsStrnCaseCmp(szValue, "no-parallel", 11)) || + (iLen == 12 && !epicsStrnCaseCmp(szValue, "not-parallel", 12)) || + (iLen == 3 && !epicsStrnCaseCmp(szValue, "old", 3))) + iMethod = pollMethodSerial; + else if ((iLen == 4 && !epicsStrnCaseCmp(szValue, "axis", 4)) || + (iLen == 13 && !epicsStrnCaseCmp(szValue, "axis-parallel", 13))) + iMethod = pollMethodAxisParallel; + else if ((iLen == 4 && !epicsStrnCaseCmp(szValue, "ctrl", 4)) || + (iLen == 8 && !epicsStrnCaseCmp(szValue, "parallel", 8)) || + (iLen == 13 && !epicsStrnCaseCmp(szValue, "ctrl-parallel", 13)) || + (iLen == 19 && !epicsStrnCaseCmp(szValue, "controller-parallel", 19))) + iMethod = pollMethodControllerParallel; + else + goto wrongValue_parallel; + } + if (iCtrlType_ != TYPE_PHYMOTION && iMethod != pollMethodDefault && iMethod != pollMethodSerial) + { epicsSnprintf(pasynUser->errorMessage, pasynUser->errorMessageSize, - "phytronController:writeOption(%s, %s) wrong axis", key, value); + "phytronController:writeOption(%s, %s) pollMethod not allowed for this controller", szKey, szValue); return asynError; } - iMethod = pollMethodDefault; + if (pAxis) + pAxis->iPollMethod_ = iMethod; + else + iDefaultPollMethod_ = iMethod; + return asynSuccess; } - if (epicsParseInt64(value, &iTmp, 0, NULL) == 0) { - if (iTmp < (pAxis ? static_cast(pollMethodDefault) : static_cast(pollMethodSerial)) || - iTmp > static_cast(pollMethodControllerParallel)) - goto wrongValue; - iMethod = static_cast(iTmp); - } else { - while (isspace(*value)) ++value; - iLen = strlen(value); - while (iLen > 0 && isspace(value[iLen - 1])) --iLen; - if (pAxis && ((iLen == 7 && !epicsStrnCaseCmp(value, "default", 7)) || - (iLen == 8 && !epicsStrnCaseCmp(value, "standard", 8)) || - (iLen == 10 && !epicsStrnCaseCmp(value, "controller", 10)))) - iMethod = pollMethodDefault; - else if ((iLen == 6 && !epicsStrnCaseCmp(value, "serial", 6)) || - (iLen == 11 && !epicsStrnCaseCmp(value, "no-parallel", 11)) || - (iLen == 12 && !epicsStrnCaseCmp(value, "not-parallel", 12)) || - (iLen == 3 && !epicsStrnCaseCmp(value, "old", 3))) - iMethod = pollMethodSerial; - else if ((iLen == 4 && !epicsStrnCaseCmp(value, "axis", 4)) || - (iLen == 13 && !epicsStrnCaseCmp(value, "axis-parallel", 13))) - iMethod = pollMethodAxisParallel; - else if ((iLen == 4 && !epicsStrnCaseCmp(value, "ctrl", 4)) || - (iLen == 8 && !epicsStrnCaseCmp(value, "parallel", 8)) || - (iLen == 13 && !epicsStrnCaseCmp(value, "ctrl-parallel", 13)) || - (iLen == 19 && !epicsStrnCaseCmp(value, "controller-parallel", 19))) - iMethod = pollMethodControllerParallel; + if (epicsStrCaseCmp(szKey, "clearAxisStatus") == 0) { + // configure "clear axis status" before move + float fTime(0.), *pfTime(&statusResetTime_); + getAddress(pasynUser, &iAxisNo); + if (iAxisNo > 0) { + pAxis = getAxis(iAxisNo); + if (!pAxis) { + epicsSnprintf(pasynUser->errorMessage, pasynUser->errorMessageSize, + "phytronController:writeOption(%s, %s) wrong axis", szKey, szValue); + return asynError; + } + pfTime = &pAxis->statusResetTime_; + } + + if (epicsParseFloat(szValue, &fTime, NULL) == 0 && fTime >= 0. && fTime <= 10.) + ; + else if (pAxis && epicsStrCaseCmp(szValue, "default") == 0) + fTime = epicsINF; + else if (epicsStrCaseCmp(szValue, "t") == 0 || epicsStrCaseCmp(szValue, "true") == 0 || + epicsStrCaseCmp(szValue, "y") == 0 || epicsStrCaseCmp(szValue, "yes") == 0 || + epicsStrCaseCmp(szValue, "on") == 0) + fTime = 0.000001; + else if (epicsStrCaseCmp(szValue, "f") == 0 || epicsStrCaseCmp(szValue, "false") == 0 || + epicsStrCaseCmp(szValue, "n") == 0 || epicsStrCaseCmp(szValue, "no") == 0 || + epicsStrCaseCmp(szValue, "off") == 0) + fTime = 0.; else { -wrongValue: epicsSnprintf(pasynUser->errorMessage, pasynUser->errorMessageSize, - "phytronController:writeOption(%s, %s) wrong value", key, value); - return asynError; + "phytronController:writeOption(%s, %s) wrong value - allowed is seconds (0..10) or boolean%s", + szKey, szValue, pAxis ? " or \"default\"" : ""); + goto wrongValue; } + *pfTime = (*pfTime <= 0.) ? -fTime : fTime; + return asynSuccess; + } + + if (epicsStrCaseCmp(szKey, "fakeHomedEnable") == 0) { + // configure "fake homed" bit: ORed real homed status with autosave position + if (epicsParseInt64(szValue, &iTmp, 0, NULL) == 0) + fake_homed_enable_ = (iTmp != 0); + else if (epicsStrCaseCmp(szValue, "t") == 0 || epicsStrCaseCmp(szValue, "true") == 0 || + epicsStrCaseCmp(szValue, "y") == 0 || epicsStrCaseCmp(szValue, "yes") == 0 || + epicsStrCaseCmp(szValue, "on") == 0) + { + const char* szSetChar((iCtrlType_ == TYPE_PHYMOTION) ? "=" : "S"); + if (sendPhytronCommand(std::string("R1001") + szSetChar + std::to_string(fake_homed_cache_[0])) != phytronSuccess) + return asynError; + fake_homed_enable_ = true; + } + else if (epicsStrCaseCmp(szValue, "f") == 0 || epicsStrCaseCmp(szValue, "false") == 0 || + epicsStrCaseCmp(szValue, "n") == 0 || epicsStrCaseCmp(szValue, "no") == 0 || + epicsStrCaseCmp(szValue, "off") == 0) + fake_homed_enable_ = false; + else + goto wrongValue_bool; + return asynSuccess; + } + + if (epicsStrCaseCmp(szKey, "allowExitOnError") == 0) { + // configure "allow exit on error": after detection of this, the IOC exists and should be restarted by procServ + if (epicsParseInt64(szValue, &iTmp, 0, NULL) == 0) + allow_exit_on_error_ = (iTmp != 0); + else if (epicsStrCaseCmp(szValue, "t") == 0 || epicsStrCaseCmp(szValue, "true") == 0 || + epicsStrCaseCmp(szValue, "y") == 0 || epicsStrCaseCmp(szValue, "yes") == 0 || + epicsStrCaseCmp(szValue, "on") == 0) + allow_exit_on_error_ = true; + else if (epicsStrCaseCmp(szValue, "f") == 0 || epicsStrCaseCmp(szValue, "false") == 0 || + epicsStrCaseCmp(szValue, "n") == 0 || epicsStrCaseCmp(szValue, "no") == 0 || + epicsStrCaseCmp(szValue, "off") == 0) + allow_exit_on_error_ = false; + else + goto wrongValue_bool; + return asynSuccess; } - if (pAxis) - pAxis->iPollMethod_ = iMethod; - else - iDefaultPollMethod_ = iMethod; - return asynSuccess; finish: - return asynMotorController::writeOption(pasynUser, key, value); + return asynMotorController::writeOption(pasynUser, szKey, szValue); + +wrongValue_parallel: + epicsSnprintf(pasynUser->errorMessage, pasynUser->errorMessageSize, + "phytronController:writeOption(%s, %s) wrong value - allowed are %sserial,axis-parallel,%sparallel", + szKey, szValue, pAxis ? "default," : "", pAxis ? "controller-" : ""); + goto wrongValue; + +wrongValue_bool: + epicsSnprintf(pasynUser->errorMessage, pasynUser->errorMessageSize, + "phytronController:writeOption(%s, %s) wrong value - allowed is boolean", szKey, szValue); + +wrongValue: + return asynError; } -/* +/** * Reset the motorEncoderRatio to 1 after the reset of MCM unit */ void phytronController::resetAxisEncoderRatio() { - for(uint32_t i = 0; i < axes.size(); i++){ - setDoubleParam(axes[i]->axisNo_, motorEncoderRatio_, 1); + for(uint32_t i = 0; i < axes_.size(); i++){ + setDoubleParam(axes_[i]->axisNo_, motorEncoderRatio_, 1); } + callParamCallbacks(); } - /** Reports on status of the driver * \param[in] fp The file pointer on which report information will be written * \param[in] level The level of report detail desired @@ -606,11 +864,16 @@ phytronAxis* phytronController::getAxis(int axisNo) * This implementation tracks, if it was called at least once. * The Option "pollMethod" configures, how to talk to the controller: * * pollMethodSerial (old and default): - * - 4 single command request and replies: motor position, encoder position, - * moving status, axis status (limit switches, home switches, ...) - * - takes about 40ms per axis + * - phyMOTION + * 4 single command request and replies: motor position, encoder position, + * moving status, axis status (limit switches, home switches, ...), + * takes about 40ms per axis + * - MCC + * 4+ single command request and replies: motor position, encoder position, + * moving status, error status and plus one limit switches in controller poll * * pollMethodAxisParallel or * * pollMethodControllerParallel: + * - phyMOTION only * - send a longer command string with multiple commands to the controller * - parses the replies of it (count should be equal to command count) * - takes about 10ms per command string, which saves time @@ -628,33 +891,104 @@ asynStatus phytronController::poll() if (iResult == asynSuccess) { // check, which axes should be handled here std::vector apTodoAxis; - for (std::vector::iterator it = axes.begin(); it != axes.end(); ++it) { - phytronAxis* pAxis(*it); - if (!pAxis) continue; - pollMethod iPollMethod(pAxis->iPollMethod_); - if (iPollMethod < 0) iPollMethod = iDefaultPollMethod_; - // this axis is configured for controller parallel poll and handled here - if (iPollMethod == pollMethodControllerParallel) - apTodoAxis.emplace_back(std::move(pAxis)); + std::vector asCommands, asResponses; + if (fake_homed_enable_ > 0) + asCommands.push_back(std::string("R1001R")); + switch (iCtrlType_) + { + case TYPE_PHYMOTION: + for (std::vector::iterator it = axes_.begin(); it != axes_.end(); ++it) { + phytronAxis* pAxis(*it); + if (!pAxis) continue; + pollMethod iPollMethod(pAxis->iPollMethod_); + if (iPollMethod < 0) iPollMethod = iDefaultPollMethod_; + // this axis is configured for controller parallel poll and handled here + if (iPollMethod == pollMethodControllerParallel) + { + apTodoAxis.emplace_back(std::move(pAxis)); + pAxis->appendRequestString(asCommands); // collect requests + } + } + break; + case TYPE_MCC: + sLastSUI_.clear(); + asCommands.push_back(std::string("SUI")); + break; + default: + return asynError; } - if (!apTodoAxis.empty()) { - std::vector asRequests, asAnswers; - asRequests.reserve(3 * apTodoAxis.size()); - // collect requests - for (std::vector::iterator it = apTodoAxis.begin(); it != apTodoAxis.end(); ++it) - (*it)->appendRequestString(asRequests); + for (std::vector::iterator it = IOs_.begin(); it != IOs_.end(); ++it) + (*it)->pollIO(asCommands, true); + if (!asCommands.empty()) + { // communicate - iResult = phyToAsyn(sendPhytronMultiCommand(asRequests, asAnswers, true)); - if (iResult == asynSuccess) { + iResult = phyToAsyn(sendPhytronMultiCommand(asCommands, asResponses, true, iCtrlType_ != TYPE_PHYMOTION)); + if (iResult == asynSuccess) + { // handle answers - for (std::vector::iterator it = apTodoAxis.begin(); it != apTodoAxis.end(); ++it) - if (!(*it)->parseAnswer(asAnswers)) + while (fake_homed_enable_) + { + double dTmp(static_cast(epicsNAN)); + const char* szValue; + if (asCommands.empty()) + { + iResult = asynError; + break; + } + szValue = asResponses.front().c_str(); + if (*szValue++ != '\x06') // need ACK here + iResult = asynError; + if (epicsParseDouble(szValue, &dTmp, NULL) != 0) + iResult = asynError; + else if (!isfinite(dTmp) || floor(dTmp + 0.5) < 0. || floor(dTmp + 0.5) >= 65536.) + iResult = asynError; + else if (static_cast(floor(dTmp + 0.5)) != fake_homed_cache_[0]) + { + // detected a difference in cached value, the Phytron was restarted + asynPrint(pasynUserSelf, ASYN_TRACE_ERROR, "detected a difference in cached fake-homed-bit value, did the Phytron a restart?\n"); + if (allow_exit_on_error_) + epicsExit(1); + fake_homed_cache_[0] = static_cast(floor(dTmp + 0.5)); + } + asResponses.erase(asResponses.begin()); + break; + } + if (iCtrlType_ == TYPE_MCC) + { + if (!asResponses.empty()) + { + sLastSUI_ = asResponses.front(); + asResponses.erase(asResponses.begin()); + if (sLastSUI_.empty()) + iResult = asynError; + else if (sLastSUI_.front() != '\x06') + iResult = asynError; + else + sLastSUI_.erase(0, 1); + } + else iResult = asynError; - if (!asAnswers.empty()) + } + else if (!apTodoAxis.empty()) // iCtrlType_ == TYPE_PHYMOTION + for (std::vector::iterator it = apTodoAxis.begin(); it != apTodoAxis.end(); ++it) + if (!(*it)->parseAnswer(asResponses)) + iResult = asynError; + for (std::vector::iterator it = IOs_.begin(); it != IOs_.end(); ++it) + if (!(*it)->pollIO(asResponses, false)) + iResult = asynError; + if (!asResponses.empty()) iResult = asynError; } + else if (!apTodoAxis.empty()) + for (std::vector::iterator it = axes_.begin(); it != axes_.end(); ++it) + { + (*it)->setIntegerParam(motorStatusProblem_, 1); // set problem bit + (*it)->callParamCallbacks(); + } } } + if (iResult != asynSuccess) + asynPrint(pasynUserSelf, ASYN_TRACE_ERROR, "phytronController::poll(%s) error %d\n", controllerName_, iResult); return iResult; } @@ -672,8 +1006,8 @@ void phytronController::epicsInithookFunction(initHookState iState) return; // before motorRecord init_record pass 1 - for (vector::iterator itC = controllers.begin(); - itC != controllers.end(); ++itC) { + for (vector::iterator itC = controllers_.begin(); + itC != controllers_.end(); ++itC) { // iterate over all controllers phytronController* pC(*itC); while (pC) { @@ -701,21 +1035,33 @@ void phytronController::epicsInithookFunction(initHookState iState) */ phytronStatus phytronController::sendPhytronCommand(std::string sCommand, std::string &sResponse, bool bACKonly) { + epicsUInt8 byCRC(0), byCRC2(0); if (sCommand.size() > 994) // maximum command length exceeded (1000 bytes incl. framing) return phytronInvalidCommand; if (sCommand.capacity() < sCommand.size() + 6) sCommand.reserve(sCommand.size() + 6); - sCommand.insert(0, "0"); // address of controller, TODO: allow different values for serial connection - sCommand.push_back(':'); // separator for CRC - epicsUInt8 byCRC(0); - for (auto it = sCommand.begin(); it != sCommand.end(); ++it) - byCRC ^= static_cast(*it); // "calculate" CRC - epicsUInt8 byCRC2(byCRC & 0x0F); - byCRC >>= 4; - // put framing incl. CRC + if (iAddress_ >= 0 && iAddress_ <= 15) + { + // address of controller + char szAddr[32]; + epicsSnprintf(szAddr, sizeof(szAddr), "%X", iAddress_); + szAddr[2] = '\0'; + sCommand.insert(0, const_cast(&szAddr[0])); + } + else + sCommand.insert(0, "0"); // default address of controller + // put framing + if (iCtrlType_ == TYPE_PHYMOTION) // phyMOTION with CRC + { + sCommand.push_back(':'); // separator for CRC + for (auto it = sCommand.begin(); it != sCommand.end(); ++it) + byCRC ^= static_cast(*it); // "calculate" CRC + byCRC2 = byCRC & 0x0F; + byCRC >>= 4; + sCommand.push_back(static_cast(byCRC + ((byCRC > 9) ? ('A' - 10) : '0'))); + sCommand.push_back(static_cast(byCRC2 + ((byCRC2 > 9) ? ('A' - 10) : '0'))); + } sCommand.insert(0, "\x02"); // STX - sCommand.push_back(static_cast(byCRC + ((byCRC > 9) ? ('A' - 10) : '0'))); - sCommand.push_back(static_cast(byCRC2 + ((byCRC2 > 9) ? ('A' - 10) : '0'))); sCommand.push_back('\x03'); // ETX // communicate @@ -761,7 +1107,7 @@ phytronStatus phytronController::sendPhytronCommand(std::string sCommand, std::s sResponse.erase(0, 1); return phytronSuccess; case '\x15': // NAK - if (bACKonly) + if (!bACKonly) return phytronSuccess; break; default: @@ -867,6 +1213,7 @@ phytronStatus phytronController::sendPhytronMultiCommand(std::vectorcontrollerName_, controllerName)) { - pAxis = new phytronAxis(controllers[i], module*10 + axis); - controllers[i]->axes.push_back(pAxis); + for(i = 0; i < phytronController::controllers_.size(); i++){ + phytronController* pC(phytronController::controllers_[i]); + if(!strcmp(pC->controllerName_, szControllerName)) { + pC->lock(); + pAxis = new phytronAxis(pC, iModule*10 + iAxis); + pC->axes_.push_back(pAxis); + pC->unlock(); break; } } //If controller is not found, report error - if(i == controllers.size()){ - printf("ERROR: phytronCreateAxis: Controller %s is not registered\n", controllerName); + if(i == phytronController::controllers_.size()){ + printf("ERROR: phytronCreateAxis: Controller %s is not registered\n", szControllerName); return asynError; } @@ -909,28 +1277,43 @@ extern "C" int phytronCreateAxis(const char* controllerName, int module, int axi } /** Configures a phytronAxis object to use a brake - * \param[in] szControllerName Name of the asyn port created by calling phytronCreateController from st.cmd + * \param[in] szControllerName Name of the asyn port created by calling phytronCreatePhymotion or phytronCreateMCC from st.cmd * \param[in] fAxis axis index (module.index) * \param[in] fOutput digital out index (module.index) or 0 to disable this function, negative value inverts output * \param[in] bDisableMotor 0=keep motor enabled, 1=disable idle motor/enable when moved * \param[in] dEngageTime time to engage brake (disable motor after this time in milliseconds, max. 10 sec) * \param[in] dReleaseTime time to release brake (start move after this time in milliseconds, max. 10 sec) */ -extern "C" int phytronBrakeOutput(const char* szControllerName, float fAxis, float fOutput, int bDisableMotor, double dEngageTime, double dReleaseTime) +int phytronAxis::phytronBrakeOutput(const char* szControllerName, float fAxis, float fOutput, int bDisableMotor, double dEngageTime, double dReleaseTime) { char szAxisNo[5]; uint32_t i, j; - epicsSnprintf(szAxisNo, sizeof(szAxisNo), "%.1f", fAxis); - szAxisNo[sizeof(szAxisNo) - 1] = '\0'; if (!szControllerName || !*szControllerName) goto failed; - for (i = 0; i < controllers.size(); ++i) { - phytronController* pC(controllers[i]); + for (i = 0; i < phytronController::controllers_.size(); ++i) { + phytronController* pC(phytronController::controllers_[i]); if (!pC || strcmp(pC->controllerName_, szControllerName) != 0) continue; // found controller, search axis - for (j = 0; j < pC->axes.size(); ++j) { - phytronAxis* pA(pC->axes[j]); + switch (pC->iCtrlType_) + { + case phytronController::TYPE_PHYMOTION: + epicsSnprintf(szAxisNo, sizeof(szAxisNo), "M%.1f", fAxis); + break; + case phytronController::TYPE_MCC: + { + int iAxis(static_cast(floor(10. * fAxis + .5))); + if (iAxis < 1 || iAxis > 8) + continue; + epicsSnprintf(szAxisNo, sizeof(szAxisNo), "%d", iAxis); + break; + } + default: + continue; + } + szAxisNo[sizeof(szAxisNo) - 1] = '\0'; + for (j = 0; j < pC->axes_.size(); ++j) { + phytronAxis* pA(pC->axes_[j]); if (!pA || strcmp(pA->axisModuleNo_, szAxisNo) != 0) continue; @@ -948,26 +1331,40 @@ extern "C" int phytronBrakeOutput(const char* szControllerName, float fAxis, flo /** Creates a new phytronAxis object. * \param[in] pC Pointer to the phytronController to which this axis belongs. - * \param[in] axisNo Index number of this axis, range 0 to pC->numAxes_-1. + * \param[in] iAxisNo Index number of this axis, range 0 to pC->numAxes_-1. * * Initializes register numbers, etc. */ -phytronAxis::phytronAxis(phytronController *pC, int axisNo) - : asynMotorAxis(pC, axisNo) +phytronAxis::phytronAxis(phytronController *pC, int iAxisNo) + : asynMotorAxis(pC, iAxisNo) , brakeOutput_(0.0) , disableMotor_(0) , brakeEngageTime_(0.0) , brakeReleaseTime_(0.0) , pC_(pC) - , lastStatus(phytronSuccess) + , lastStatus_(phytronSuccess) , brakeReleased_(0) , iPollMethod_(pollMethodDefault) , homeState_(0) + , statusResetTime_(epicsINF) { //Controller always supports encoder. Encoder enable/disable is set through UEIP setIntegerParam(pC_->motorStatusHasEncoder_, 1); setDoubleParam(pC_->motorEncoderRatio_, 1); - epicsSnprintf(axisModuleNo_, sizeof(axisModuleNo_), "%.1f", axisNo / 10.); + axisModuleNo_[0] = '\0'; + switch (pC_->iCtrlType_) + { + case phytronController::TYPE_PHYMOTION: + epicsSnprintf(axisModuleNo_, sizeof(axisModuleNo_), "M%.1f", iAxisNo / 10.); + break; + case phytronController::TYPE_MCC: + { + iAxisNo %= 10; + if (iAxisNo >= 1 && iAxisNo <= 8) + epicsSnprintf(axisModuleNo_, sizeof(axisModuleNo_), "%d", iAxisNo); + break; + } + } axisModuleNo_[sizeof(axisModuleNo_) - 1] = '\0'; } @@ -979,15 +1376,45 @@ phytronAxis::phytronAxis(phytronController *pC, int axisNo) */ void phytronAxis::report(FILE *fp, int level) { - if (level > 0) { - fprintf(fp, " axis %d\n", - axisNo_); - } + if (level > 0) + fprintf(fp, " axis %d\n", axisNo_); // Call the base class method asynMotorAxis::report(fp, level); } +/** + * Clears axis errors before next try to move. + * \param[in,out] pCmdList list of commands to append to or NULL (execute directly) + * \return communication status + */ +phytronStatus phytronAxis::clearAxisError(std::vector* pCmdList) +{ + float fTime(statusResetTime_); + if (pC_->iCtrlType_ != phytronController::TYPE_PHYMOTION ) + return phytronSuccess; // not supported + if (!isfinite(fTime) && fTime > 0.) + fTime = fabs(pC_->statusResetTime_); + if (fTime <= 0.0) + return phytronSuccess; // not needed + if (fTime > 10.) + fTime = 10.; + statusResetTime_ = -statusResetTime_; + if (fTime >= 0.001) // reset status and wait + { + phytronStatus iResult(pC_->sendPhytronCommand(std::string("SEC") + &axisModuleNo_[1])); + epicsThreadSleep(fTime); + return iResult; + } + if (pCmdList) + { + pCmdList->push_back(std::string("SEC") + &axisModuleNo_[1]); + return phytronSuccess; + } + else + return pC_->sendPhytronCommand(std::string("SEC") + &axisModuleNo_[1]); +} + /** Sets velocity parameters before the move is executed. Controller produces a * trapezoidal speed profile defined by these parmeters. * \param[in,out] pCmdList list of commands to append to or NULL (execute directly) @@ -997,10 +1424,11 @@ void phytronAxis::report(FILE *fp, int level) */ phytronStatus phytronAxis::setVelocity(std::vector* pCmdList, double minVelocity, double maxVelocity, int moveType) { - std::vector asRequests; + std::vector asCommands; enum pollMethod iPollMethod(iPollMethod_); + const char* szSetChar((pC_->iCtrlType_ == phytronController::TYPE_PHYMOTION) ? "=" : "S"); if (iPollMethod < 0) iPollMethod = pC_->iDefaultPollMethod_; - if (!pCmdList) pCmdList = &asRequests; + if (!pCmdList) pCmdList = &asCommands; maxVelocity = fabs(maxVelocity); minVelocity = fabs(minVelocity); @@ -1031,18 +1459,18 @@ phytronStatus phytronAxis::setVelocity(std::vector* pCmdList, doubl switch (moveType) { case stdMove: //Set maximum velocity (P14) and minimum velocity (P04) - pCmdList->push_back(std::string("M") + axisModuleNo_ + "P14=" + std::to_string(maxVelocity)); - pCmdList->push_back(std::string("M") + axisModuleNo_ + "P04=" + std::to_string(minVelocity)); + pCmdList->push_back(std::string(axisModuleNo_) + "P14" + szSetChar + std::to_string(maxVelocity)); + pCmdList->push_back(std::string(axisModuleNo_) + "P04" + szSetChar + std::to_string(minVelocity)); break; case homeMove: //Set maximum velocity (P08) and minimum velocity (P10) - pCmdList->push_back(std::string("M") + axisModuleNo_ + "P08=" + std::to_string(maxVelocity)); - pCmdList->push_back(std::string("M") + axisModuleNo_ + "P10=" + std::to_string(minVelocity)); + pCmdList->push_back(std::string(axisModuleNo_) + "P08" + szSetChar + std::to_string(maxVelocity)); + pCmdList->push_back(std::string(axisModuleNo_) + "P10" + szSetChar + std::to_string(minVelocity)); break; default: return phytronSuccess; } - if (pCmdList != &asRequests) return phytronSuccess; - return pC_->sendPhytronMultiCommand(asRequests, asRequests, false, iPollMethod == pollMethodSerial); + if (pCmdList != &asCommands) return phytronSuccess; + return pC_->sendPhytronMultiCommand(asCommands, asCommands, false, iPollMethod == pollMethodSerial); } /** Sets acceleration parameters before the move is executed. @@ -1053,6 +1481,7 @@ phytronStatus phytronAxis::setVelocity(std::vector* pCmdList, doubl phytronStatus phytronAxis::setAcceleration(std::vector* pCmdList, double acceleration, int moveType) { std::string sCommand; + const char* szSetChar((pC_->iCtrlType_ == phytronController::TYPE_PHYMOTION) ? "=" : "S"); if (acceleration > MAX_ACCELERATION) { acceleration = MAX_ACCELERATION; asynPrint(pasynUser_, ASYN_TRACE_WARNING, @@ -1064,8 +1493,8 @@ phytronStatus phytronAxis::setAcceleration(std::vector* pCmdList, d "phytronAxis::setAcceleration: Failed for axis %d - Acceleration %f is to low, " "setting to minimum acceleration: %d!\n", axisNo_, acceleration, MIN_ACCELERATION); } - sCommand = std::string("M") + axisModuleNo_ + ((moveType == homeMove) ? "P09=" : "P15=") + - std::to_string(acceleration); + sCommand = std::string(axisModuleNo_) + ((moveType == homeMove) ? "P09" : "P15") + + szSetChar + std::to_string(acceleration); if (!pCmdList) return pC_->sendPhytronCommand(sCommand); pCmdList->push_back(sCommand); @@ -1101,25 +1530,28 @@ asynStatus phytronAxis::configureBrake(float fOutput, bool bDisableMotor, double */ phytronStatus phytronAxis::setBrakeOutput(std::vector* pCmdList, bool bWantToMoveMotor) { - std::vector asRequests; + std::vector asCommands; //printf("setting brake output for axis %s: wantmove=%d output=%.15g disable=%d engagetime=%.15g releasetime=%.15g released=%d\n", // axisModuleNo_, bWantToMoveMotor, brakeOutput_, disableMotor_, brakeEngageTime_, brakeReleaseTime_, brakeReleased_); - if (!pCmdList) pCmdList = &asRequests; + if (!pCmdList) pCmdList = &asCommands; brakeReleased_ = bWantToMoveMotor; if (bWantToMoveMotor) { // activate motor output if (!(disableMotor_ & 2)) { - pCmdList->push_back(std::string("M") + axisModuleNo_ + "MA"); + pCmdList->push_back(std::string(axisModuleNo_) + "MA"); disableMotor_ |= 2; } if (fabs(brakeOutput_) > 0.0) { // release brake - sprintf(pC_->outString_, "A%.1f%c", fabs(brakeOutput_), brakeOutput_ > 0.0 ? 'S' : 'R'); + if (pC_->iCtrlType_ == phytronController::TYPE_PHYMOTION) + sprintf(pC_->outString_, "A%.1f%c", fabs(brakeOutput_), brakeOutput_ > 0.0 ? 'S' : 'R'); + else + sprintf(pC_->outString_, "A%d%c", static_cast(fabs(10. * brakeOutput_ + 0.5)) % 10, brakeOutput_ > 0.0 ? 'S' : 'R'); pCmdList->push_back(const_cast(pC_->outString_)); } - if (pCmdList == &asRequests || brakeReleaseTime_ > 0.0) { - pC_->sendPhytronMultiCommand(*pCmdList, asRequests, false, false); + if (pCmdList == &asCommands || brakeReleaseTime_ > 0.0) { + pC_->sendPhytronMultiCommand(*pCmdList, asCommands, false, false); pCmdList->clear(); } @@ -1130,10 +1562,13 @@ phytronStatus phytronAxis::setBrakeOutput(std::vector* pCmdList, bo if (fabs(brakeOutput_) > 0.0) { // engage brake - sprintf(pC_->outString_, "A%.1f%c", fabs(brakeOutput_), brakeOutput_ > 0.0 ? 'R' : 'S'); + if (pC_->iCtrlType_ == phytronController::TYPE_PHYMOTION) + sprintf(pC_->outString_, "A%.1f%c", fabs(brakeOutput_), brakeOutput_ > 0.0 ? 'R' : 'S'); + else + sprintf(pC_->outString_, "A%d%c", static_cast(fabs(10. * brakeOutput_ + 0.5)) % 10, brakeOutput_ > 0.0 ? 'R' : 'S'); pCmdList->push_back(const_cast(pC_->outString_)); - if (pCmdList == &asRequests || brakeEngageTime_ > 0.0) { - pC_->sendPhytronMultiCommand(*pCmdList, asRequests, false, false); + if (pCmdList == &asCommands || brakeEngageTime_ > 0.0) { + pC_->sendPhytronMultiCommand(*pCmdList, asCommands, false, false); pCmdList->clear(); } @@ -1142,11 +1577,10 @@ phytronStatus phytronAxis::setBrakeOutput(std::vector* pCmdList, bo } if (disableMotor_ & 1) { // deactivate motor output - sprintf(pC_->outString_, "M%sMD", axisModuleNo_); - pCmdList->push_back(const_cast(pC_->outString_)); + pCmdList->push_back(std::string(axisModuleNo_) + "MD"); disableMotor_ &= ~2; - if (pCmdList == &asRequests) { - pC_->sendPhytronMultiCommand(*pCmdList, asRequests, false, false); + if (pCmdList == &asCommands) { + pC_->sendPhytronMultiCommand(*pCmdList, asCommands, false, false); pCmdList->clear(); } } @@ -1167,6 +1601,9 @@ asynStatus phytronAxis::move(double position, int relative, double minVelocity, enum pollMethod iPollMethod(iPollMethod_); if (iPollMethod < 0) iPollMethod = pC_->iDefaultPollMethod_; + phyStatus = clearAxisError(&asCommands); + CHECK_AXIS("move", "Clearing motor errors", this, return pC_->phyToAsyn(phyStatus)); + //NOTE: Check if velocity is different, before setting it. phyStatus = setVelocity(&asCommands, minVelocity, maxVelocity, stdMove); CHECK_AXIS("move", "Setting the velocity", this, return pC_->phyToAsyn(phyStatus)); @@ -1179,9 +1616,9 @@ asynStatus phytronAxis::move(double position, int relative, double minVelocity, CHECK_AXIS("move", "Setting the brake", this, return pC_->phyToAsyn(phyStatus)); if (relative) { - sprintf(pC_->outString_, "M%s%c%d", axisModuleNo_, position>0 ? '+':'-', abs(NINT(position))); + sprintf(pC_->outString_, "%s%c%d", axisModuleNo_, position>0 ? '+':'-', abs(NINT(position))); } else { - sprintf(pC_->outString_, "M%sA%d", axisModuleNo_, NINT(position)); + sprintf(pC_->outString_, "%sA%d", axisModuleNo_, NINT(position)); } asCommands.push_back(const_cast(pC_->outString_)); @@ -1201,13 +1638,15 @@ asynStatus phytronAxis::home(double minVelocity, double maxVelocity, double acce { std::vector asCommands; phytronStatus phyStatus; - int homingType; + int homingType(-1); enum pollMethod iPollMethod(iPollMethod_); + const char* szSetChar((pC_->iCtrlType_ == phytronController::TYPE_PHYMOTION) ? "=" : "S"); if (iPollMethod < 0) iPollMethod = pC_->iDefaultPollMethod_; pC_->getIntegerParam(axisNo_, pC_->homingProcedure_, &homingType); - if (homingType == limit) - homeState_ = forwards ? 3 : 2; + + phyStatus = clearAxisError(&asCommands); + CHECK_AXIS("move", "Clearing motor errors", this, return pC_->phyToAsyn(phyStatus)); phyStatus = setVelocity(&asCommands, minVelocity, maxVelocity, homeMove); CHECK_AXIS("home", "Setting the velocity", this, return pC_->phyToAsyn(phyStatus)); @@ -1218,24 +1657,44 @@ asynStatus phytronAxis::home(double minVelocity, double maxVelocity, double acce setBrakeOutput(&asCommands, 1); CHECK_AXIS("home", "Setting the brake", this, return pC_->phyToAsyn(phyStatus)); + if (axisNo_>= 10 && (axisNo_ / 10) <= ARRAY_SIZE(pC_->fake_homed_cache_)) + { + int iIndex((axisNo_ / 10) - 1); + if ((pC_->fake_homed_cache_[iIndex] >> (axisNo_ % 10)) & 1) + { + pC_->fake_homed_cache_[iIndex] &= ~(1 << (axisNo_ % 10)); + if (pC_->fake_homed_enable_) + { + if (!iIndex) + pC_->fake_homed_cache_[0] |= 1; + asCommands.push_back(std::string("R") + std::to_string(1001 + iIndex) + szSetChar + + std::to_string(pC_->fake_homed_cache_[iIndex])); + } + } + } + + if (homingType == limit) + homeState_ = forwards ? 3 : 2; if(forwards){ - if(homingType == limit) sprintf(pC_->outString_, "M%sR+", axisModuleNo_); - else if(homingType == center) sprintf(pC_->outString_, "M%sR+C", axisModuleNo_); - else if(homingType == encoder) sprintf(pC_->outString_, "M%sR+I", axisModuleNo_); - else if(homingType == limitEncoder) sprintf(pC_->outString_, "M%sR+^I", axisModuleNo_); - else if(homingType == centerEncoder) sprintf(pC_->outString_, "M%sR+C^I", axisModuleNo_); + if(homingType == limit) sprintf(pC_->outString_, "%sR+", axisModuleNo_); + else if(homingType == center) sprintf(pC_->outString_, "%sR+C", axisModuleNo_); + else if(homingType == encoder) sprintf(pC_->outString_, "%sR+I", axisModuleNo_); + else if(homingType == limitEncoder) sprintf(pC_->outString_, "%sR+^I", axisModuleNo_); + else if(homingType == centerEncoder) sprintf(pC_->outString_, "%sR+C^I", axisModuleNo_); //Homing procedures for rotational movements (no hardware limit switches) - else if(homingType == referenceCenter) sprintf(pC_->outString_, "M%sRC+", axisModuleNo_); - else if(homingType == referenceCenterEncoder) sprintf(pC_->outString_, "M%sRC+^I", axisModuleNo_); + else if(homingType == referenceCenter) sprintf(pC_->outString_, "%sRC+", axisModuleNo_); + else if(homingType == referenceCenterEncoder) sprintf(pC_->outString_, "%sRC+^I", axisModuleNo_); + else return asynError; } else { - if(homingType == limit) sprintf(pC_->outString_, "M%sR-", axisModuleNo_); - else if(homingType == center) sprintf(pC_->outString_, "M%sR-C", axisModuleNo_); - else if(homingType == encoder) sprintf(pC_->outString_, "M%sR-I", axisModuleNo_); - else if(homingType == limitEncoder) sprintf(pC_->outString_, "M%sR-^I", axisModuleNo_); - else if(homingType == centerEncoder) sprintf(pC_->outString_, "M%sR-C^I", axisModuleNo_); + if(homingType == limit) sprintf(pC_->outString_, "%sR-", axisModuleNo_); + else if(homingType == center) sprintf(pC_->outString_, "%sR-C", axisModuleNo_); + else if(homingType == encoder) sprintf(pC_->outString_, "%sR-I", axisModuleNo_); + else if(homingType == limitEncoder) sprintf(pC_->outString_, "%sR-^I", axisModuleNo_); + else if(homingType == centerEncoder) sprintf(pC_->outString_, "%sR-C^I", axisModuleNo_); //Homing procedures for rotational movements (no hardware limit switches) - else if(homingType == referenceCenter) sprintf(pC_->outString_, "M%sRC-", axisModuleNo_); - else if(homingType == referenceCenterEncoder) sprintf(pC_->outString_, "M%sRC-^I", axisModuleNo_); + else if(homingType == referenceCenter) sprintf(pC_->outString_, "%sRC-", axisModuleNo_); + else if(homingType == referenceCenterEncoder) sprintf(pC_->outString_, "%sRC-^I", axisModuleNo_); + else return asynError; } asCommands.push_back(const_cast(pC_->outString_)); @@ -1257,6 +1716,9 @@ asynStatus phytronAxis::moveVelocity(double minVelocity, double maxVelocity, dou enum pollMethod iPollMethod(iPollMethod_); if (iPollMethod < 0) iPollMethod = pC_->iDefaultPollMethod_; + phyStatus = clearAxisError(&asCommands); + CHECK_AXIS("move", "Clearing motor errors", this, return pC_->phyToAsyn(phyStatus)); + phyStatus = setVelocity(&asCommands, minVelocity, maxVelocity, stdMove); CHECK_AXIS("moveVelocity", "Setting the velocity", this, ); @@ -1265,7 +1727,7 @@ asynStatus phytronAxis::moveVelocity(double minVelocity, double maxVelocity, dou setBrakeOutput(&asCommands, 1); - asCommands.push_back(std::string("M") + axisModuleNo_ + (maxVelocity < 0. ? "L-" : "L+")); + asCommands.push_back(std::string(axisModuleNo_) + (maxVelocity < 0. ? "L-" : "L+")); phyStatus = pC_->sendPhytronMultiCommand(asCommands, asCommands, false, iPollMethod == pollMethodSerial); CHECK_AXIS("moveVelocity", "Start move", this, return pC_->phyToAsyn(phyStatus)); @@ -1286,7 +1748,7 @@ asynStatus phytronAxis::stop(double acceleration) phyStatus = setAcceleration(&asCommands, acceleration, stopMove); CHECK_AXIS("stop", "Setting the acceleration", this, ); - asCommands.push_back(std::string("M") + axisModuleNo_ + "S"); + asCommands.push_back(std::string(axisModuleNo_) + "S"); phyStatus = pC_->sendPhytronMultiCommand(asCommands, asCommands, false, iPollMethod == pollMethodSerial); CHECK_AXIS("stop", "Stop move", this, return pC_->phyToAsyn(phyStatus)); @@ -1299,17 +1761,20 @@ asynStatus phytronAxis::setEncoderRatio(double ratio) phytronStatus phyStatus; double encoderPosition; std::string sResponse; + const char* szSetChar((pC_->iCtrlType_ == phytronController::TYPE_PHYMOTION) ? "=" : "S"); - phyStatus = pC_->sendPhytronCommand(std::string("M") + axisModuleNo_ + "P22R", sResponse); + phyStatus = pC_->sendPhytronCommand(std::string(axisModuleNo_) + "P22R", sResponse); if (phyStatus == phytronSuccess) if (epicsParseDouble(sResponse.c_str(), &encoderPosition, NULL) != 0) phyStatus = phytronInvalidReturn; CHECK_AXIS("setEncoderRatio", "Reading encoder position", this, return pC_->phyToAsyn(phyStatus)); - phyStatus = pC_->sendPhytronCommand(std::string("M") + axisModuleNo_ + "P39=" + std::to_string(1. / ratio), sResponse); + phyStatus = pC_->sendPhytronCommand(std::string(axisModuleNo_) + "P39" + szSetChar + + std::to_string(1. / ratio), sResponse); CHECK_AXIS("setEncoderRatio", "Setting ratio", this, return pC_->phyToAsyn(phyStatus)); setDoubleParam(pC_->motorEncoderPosition_, encoderPosition * ratio); + callParamCallbacks(); return asynSuccess; } @@ -1325,9 +1790,26 @@ asynStatus phytronAxis::setEncoderPosition(double position) asynStatus phytronAxis::setPosition(double position) { phytronStatus phyStatus; - - sprintf(pC_->outString_, "M%sP20=%f", axisModuleNo_, position); - phyStatus = pC_->sendPhytronCommand(const_cast(pC_->outString_)); + std::vector asCommands; + const char* szSetChar((pC_->iCtrlType_ == phytronController::TYPE_PHYMOTION) ? "=" : "S"); + + asCommands.push_back(std::string(axisModuleNo_) + "P20" + szSetChar + std::to_string(position)); + if (axisNo_>= 10 && (axisNo_ / 10) <= ARRAY_SIZE(pC_->fake_homed_cache_)) + { + int iIndex((axisNo_ / 10) - 1); + if (!((pC_->fake_homed_cache_[iIndex] >> (axisNo_ % 10)) & 1)) + { + pC_->fake_homed_cache_[iIndex] |= 1 << (axisNo_ % 10); + if (pC_->fake_homed_enable_) + { + if (!iIndex) + pC_->fake_homed_cache_[0] |= 1; + asCommands.push_back(std::string("R") + std::to_string(1001 + iIndex) + szSetChar + + std::to_string(pC_->fake_homed_cache_[iIndex])); + } + } + } + phyStatus = pC_->sendPhytronMultiCommand(asCommands, asCommands, false, iPollMethod_ == pollMethodSerial); CHECK_AXIS("setPosition", "Setting motor position", this, return pC_->phyToAsyn(phyStatus)); return asynSuccess; @@ -1336,11 +1818,16 @@ asynStatus phytronAxis::setPosition(double position) /** Polls the axis depending on configuration (asynSetOption "pollMethod"). * pollMethodDefault: apply controller setting for this axis (see below) * pollMethodSerial (old or default): - * - 4 single command request and replies: motor position, encoder position, - * moving status, axis status (limit switches, home switches, ...) - * - takes about 40ms per axis + * - phyMOTION + * 4 single command request and replies: motor position, encoder position, + * moving status, axis status (limit switches, home switches, ...), + * takes about 40ms per axis + * - MCC + * 4+ single command request and replies: motor position, encoder position, + * moving status, error status and plus one limit switches in controller poll * pollMethodAxisParallel: * pollMethodControllerParallel: + * - phyMOTION only * - send a longer command string with multiple commands to the controller * - parses the replies of it (count should be equal to command count) * - takes about 10ms per command string and reply string, which saves time @@ -1356,20 +1843,24 @@ asynStatus phytronAxis::poll(bool *moving) { int iDone(0); enum pollMethod iPollMethod(iPollMethod_); - std::vector asRequests, asAnswers; + std::vector asCommands, asResponses; phytronStatus phyStatus(phytronSuccess); if (iPollMethod < 0) iPollMethod = pC_->iDefaultPollMethod_; switch (iPollMethod) { case pollMethodSerial: case pollMethodAxisParallel: - appendRequestString(asRequests); // get commands + appendRequestString(asCommands); // get commands // communicate - phyStatus = pC_->sendPhytronMultiCommand(asRequests, asAnswers, false, iPollMethod == pollMethodSerial); + phyStatus = pC_->sendPhytronMultiCommand(asCommands, asResponses, false, iPollMethod == pollMethodSerial); if (phyStatus == phytronSuccess) // on success: parse answers - if (!parseAnswer(asAnswers) || !asAnswers.empty()) - phyStatus = phytronInvalidReturn; + { + if (!parseAnswer(asResponses) || !asResponses.empty()) + phyStatus = phytronInvalidReturn; + } + else + setIntegerParam(pC_->motorStatusProblem_, 1); // set problem bit CHECK_AXIS("poll", "Reading axis", this, setIntegerParam(pC_->motorStatusProblem_, 1); \ - callParamCallbacks(); pC_->phyToAsyn(phyStatus)); + callParamCallbacks()); break; default: break; @@ -1390,17 +1881,28 @@ void phytronAxis::appendRequestString(std::vector &asCommands) cons enum pollMethod iPollMethod(iPollMethod_); if (iPollMethod < 0) iPollMethod = pC_->iDefaultPollMethod_; const char* pszAxis(&axisModuleNo_[0]); - if (iPollMethod == pollMethodSerial) { - // old default method, which uses 4 requests - asCommands.push_back(std::string("M") + pszAxis + "P20R"); - asCommands.push_back(std::string("M") + pszAxis + "P22R"); - asCommands.push_back(std::string("M") + pszAxis + "==H"); - asCommands.push_back(std::string("SE") + pszAxis); - } else { - // new method: fix race condition and substitutes "==H" with result of "SE"-bit 0 - asCommands.push_back(std::string("SE") + pszAxis); - asCommands.push_back(std::string("M") + pszAxis + "P20R"); - asCommands.push_back(std::string("M") + pszAxis + "P22R"); + switch (pC_->iCtrlType_) + { + case phytronController::TYPE_PHYMOTION: + if (iPollMethod == pollMethodSerial) { + // old default method, which uses 4 requests + asCommands.push_back(std::string(pszAxis) + "P20R"); + asCommands.push_back(std::string(pszAxis) + "P22R"); + asCommands.push_back(std::string(pszAxis) + "==H"); + asCommands.push_back(std::string("SE") + &pszAxis[1]); + } else { + // new method: fix race condition and substitutes "==H" with result of "SE"-bit 0 + asCommands.push_back(std::string("SE") + &pszAxis[1]); + asCommands.push_back(std::string(pszAxis) + "P20R"); + asCommands.push_back(std::string(pszAxis) + "P22R"); + } + break; + case phytronController::TYPE_MCC: + asCommands.push_back(std::string(pszAxis) + "=H"); // idle + asCommands.push_back(std::string(pszAxis) + "=E"); // power-stage error + asCommands.push_back(std::string(pszAxis) + "P20R"); + asCommands.push_back(std::string(pszAxis) + "P22R"); + break; } } @@ -1415,34 +1917,82 @@ bool phytronAxis::parseAnswer(std::vector &asValues) { enum pollMethod iPollMethod(iPollMethod_); if (iPollMethod < 0) iPollMethod = pC_->iDefaultPollMethod_; - int iAxisStatus, iMotIdx, iEncIdx, iStatIdx, iIdleIdx, iHighLimit, iLowLimit; - size_t iRemoveCount; + int iAxisStatus(0), iMotIdx(-1), iEncIdx(-1), iStatIdx(-1), iIdleIdx(-1), iHighLimit(0), iLowLimit(0); + size_t iRemoveCount(static_cast(-1)); bool bResult(false), bMoving; double dRatio; epicsInt32 iOldDone(0); - if (iPollMethod == pollMethodSerial) { - // old default method, which uses 4 single requests - iRemoveCount = 4; - if (asValues.size() != iRemoveCount) goto finish; - iMotIdx = 0; // Mm.aP20R - iEncIdx = 1; // Mm.aP22R - iIdleIdx = 2; // Mm.a==H - iStatIdx = 3; // SEm.a - } else { - // new method: fix race condition and substitutes "==H" with result of "SE"-bit 0 - iRemoveCount = 3; - if (asValues.size() < iRemoveCount) goto finish; - iStatIdx = 0; // SEm.a - iMotIdx = 1; // Mm.aP20R - iEncIdx = 2; // Mm.aP22R - iIdleIdx = -1; // not used - } - if (iPollMethod == pollMethodControllerParallel) { - for (size_t i = 0; i < iRemoveCount; ++i) { - if (asValues[i].substr(0, 1) != "\x06") // no ACK found here + switch (pC_->iCtrlType_) + { + case phytronController::TYPE_PHYMOTION: + if (iPollMethod == pollMethodSerial) { + // old default method, which uses 4 single requests + iRemoveCount = 4; + if (asValues.size() != iRemoveCount) goto finish; + iMotIdx = 0; // Mm.aP20R + iEncIdx = 1; // Mm.aP22R + iIdleIdx = 2; // Mm.a==H + iStatIdx = 3; // SEm.a + } else { + // new method: fix race condition and substitutes "==H" with result of "SE"-bit 0 + iRemoveCount = 3; + if (asValues.size() < iRemoveCount) goto finish; + iStatIdx = 0; // SEm.a + iMotIdx = 1; // Mm.aP20R + iEncIdx = 2; // Mm.aP22R + iIdleIdx = -1; // not used + } + if (asValues.size() < iRemoveCount) + goto finish; + if (iPollMethod == pollMethodControllerParallel) { + for (size_t i = 0; i < iRemoveCount; ++i) { + if (asValues[i].substr(0, 1) != "\x06") // no ACK found here + goto finish; + asValues[i].erase(0, 1); + } + } + break; + case phytronController::TYPE_MCC: + { + int iAxis(-1); + if (asValues.size() < 2) goto finish; - asValues[i].erase(0, 1); + iRemoveCount = 4; + switch (axisModuleNo_[0]) + { + case 'X': case 'x': case '1': iAxis = 0; break; + case 'Y': case 'y': case '2': iAxis = 1; break; + case 'Z': case 'z': case '3': iAxis = 2; break; + case 'W': case 'w': case '4': iAxis = 3; break; + case 'A': case 'a': case '5': iAxis = 4; break; + case 'B': case 'b': case '6': iAxis = 5; break; + case 'C': case 'c': case '7': iAxis = 6; break; + case 'D': case 'd': case '8': iAxis = 7; break; + default: goto finish; + } + if (pC_->sLastSUI_.size() > static_cast(iAxis + 2) && pC_->sLastSUI_[0] == 'I' && pC_->sLastSUI_[1] == '=') + { + switch (pC_->sLastSUI_[2 + iAxis]) + { + case '0': iAxisStatus = 0x00; break; // no limit switch + case '2': iAxisStatus = 0x30; break; // both limit switches + case '+': iAxisStatus = 0x10; break; // positive limit switch + case '-': iAxisStatus = 0x20; break; // negative limit switch + default: goto finish; + } + } + else + goto finish; + if (asValues[1].substr(0, 1) == "E") // X=E power-stage error + iAxisStatus |= 0x2000; // power stage error + iIdleIdx = 0; // "X=H" + iStatIdx = -1; + iMotIdx = 2; // XP20R + iEncIdx = 3; // XP22R + break; } + default: + goto finish; } // current motor position @@ -1456,12 +2006,17 @@ bool phytronAxis::parseAnswer(std::vector &asValues) setDoubleParam(pC_->motorEncoderPosition_, atof(asValues[iEncIdx].c_str()) * dRatio); // motor status - iAxisStatus = atoi(asValues[iStatIdx].c_str()); + if (iStatIdx >= 0) + iAxisStatus = atoi(asValues[iStatIdx].c_str()); // idle/moving status pC_->getIntegerParam(axisNo_, pC_->motorStatusDone_, &iOldDone); if (iIdleIdx >= 0) + { bMoving = (asValues[iIdleIdx].substr(0, 1) != "E"); + if (iStatIdx < 0 && bMoving) + iAxisStatus |= 0x01; + } else bMoving = (iAxisStatus & 1) != 0; setIntegerParam(pC_->motorStatusDone_, bMoving ? 0 : 1); @@ -1472,10 +2027,13 @@ bool phytronAxis::parseAnswer(std::vector &asValues) iLowLimit = (iAxisStatus & 0x20) ? 1 : 0; if (homeState_ >> 1) { // workaround for home on limit switch + int iHomingType(-1); + bResult = !(iAxisStatus & 0xE800); // axis internal/power-stage/SFI/ENDAT error if (homeState_ & 1) iHighLimit = 0; else iLowLimit = 0; + pC_->getIntegerParam(axisNo_, pC_->homingProcedure_, &iHomingType); switch (homeState_ >> 1) { case 1: // homing started, wait for moving if (bMoving) @@ -1492,21 +2050,38 @@ bool phytronAxis::parseAnswer(std::vector &asValues) iLowLimit = 1; } if ((homeState_ >> 1) >= 4) + { + // end-of-reference: in case of triggered limit switches, Phytron + // sets "limit switch error", which should be cleared, if no + // other problem was detected [manual SEm.n] + int iMask((homeState_ & 1) ? 0xFA29 : 0xFA19); + if (pC_->iCtrlType_ == phytronController::TYPE_PHYMOTION && (iAxisStatus & iMask) == 0x1208) + bResult = (pC_->sendPhytronCommand(std::string("SEC") + &axisModuleNo_[1]) == phytronSuccess); homeState_ = 0; + } break; default: homeState_ = 0; break; } } + else + bResult = !(iAxisStatus & 0xE800); // axis internal/power-stage/SFI/ENDAT error + if ((iAxisStatus & 0x1000) != 0 && statusResetTime_ < 0.) // axis limit-switch error + statusResetTime_ = -statusResetTime_; setIntegerParam(pC_->motorStatusHighLimit_, iHighLimit); setIntegerParam(pC_->motorStatusLowLimit_, iLowLimit); setIntegerParam(pC_->motorStatusAtHome_, (iAxisStatus & 0x40)/0x40); - setIntegerParam(pC_->motorStatusHomed_, (iAxisStatus & 0x08)/0x08); + setIntegerParam(pC_->motorStatusHomed_, 0); + if (pC_->fake_homed_enable_ && axisNo_ >= 10 && + (axisNo_ / 10) <= ARRAY_SIZE(pC_->fake_homed_cache_) && + ((pC_->fake_homed_cache_[(axisNo_ / 10) - 1] >> (axisNo_ % 10)) & 1)) + setIntegerParam(pC_->motorStatusHomed_, 1); // after setPosition: set HOMED bit + else + setIntegerParam(pC_->motorStatusHomed_, (iAxisStatus & 0x08)/0x08); setIntegerParam(pC_->motorStatusHome_, (iAxisStatus & 0x08)/0x08); setIntegerParam(pC_->motorStatusSlip_, (iAxisStatus & 0x4000)/0x4000); setIntegerParam(pC_->axisStatus_, iAxisStatus); // Update the axis status record ($(P)$(M)_STATUS) - bResult = true; finish: setIntegerParam(pC_->motorStatusProblem_, bResult ? 0 : 1); @@ -1514,69 +2089,3 @@ bool phytronAxis::parseAnswer(std::vector &asValues) asValues.erase(asValues.begin(), asValues.begin() + iRemoveCount); return bResult; } - -/** Parameters for iocsh phytron axis registration*/ -static const iocshArg phytronCreateAxisArg0 = {"Controller Name", iocshArgString}; -static const iocshArg phytronCreateAxisArg1 = {"Module index", iocshArgInt}; -static const iocshArg phytronCreateAxisArg2 = {"Axis index", iocshArgInt}; -static const iocshArg* const phytronCreateAxisArgs[] = {&phytronCreateAxisArg0, - &phytronCreateAxisArg1, - &phytronCreateAxisArg2}; - -/** Parameters for iocsh phytron controller registration */ -static const iocshArg phytronCreateControllerArg0 = {"Port name", iocshArgString}; -static const iocshArg phytronCreateControllerArg1 = {"PhytronAxis port name", iocshArgString}; -static const iocshArg phytronCreateControllerArg2 = {"Moving poll period (ms)", iocshArgInt}; -static const iocshArg phytronCreateControllerArg3 = {"Idle poll period (ms)", iocshArgInt}; -static const iocshArg phytronCreateControllerArg4 = {"Timeout (ms)", iocshArgDouble}; -static const iocshArg phytronCreateControllerArg5 = {"Do not restart controller with IOC", iocshArgInt}; -static const iocshArg * const phytronCreateControllerArgs[] = {&phytronCreateControllerArg0, - &phytronCreateControllerArg1, - &phytronCreateControllerArg2, - &phytronCreateControllerArg3, - &phytronCreateControllerArg4, - &phytronCreateControllerArg5}; - -/** Parameters for iocsh phytron brake(s) output registration */ -static const iocshArg phytronBrakeOutputArg0 = {"Controller Name", iocshArgString}; -static const iocshArg phytronBrakeOutputArg1 = {"Axis index", iocshArgDouble}; -static const iocshArg phytronBrakeOutputArg2 = {"Output index", iocshArgDouble}; -static const iocshArg phytronBrakeOutputArg3 = {"Motor disable flag", iocshArgInt}; -static const iocshArg phytronBrakeOutputArg4 = {"Brake engage wait time (ms)", iocshArgDouble}; -static const iocshArg phytronBrakeOutputArg5 = {"Brake release wait time (ms)", iocshArgDouble}; -static const iocshArg* const phytronBrakeOutputArgs[] = {&phytronBrakeOutputArg0, - &phytronBrakeOutputArg1, - &phytronBrakeOutputArg2, - &phytronBrakeOutputArg3, - &phytronBrakeOutputArg4, - &phytronBrakeOutputArg5}; - -static const iocshFuncDef phytronCreateAxisDef = {"phytronCreateAxis", 3, phytronCreateAxisArgs}; -static const iocshFuncDef phytronCreateControllerDef = {"phytronCreateController", 6, phytronCreateControllerArgs}; -static const iocshFuncDef phytronBrakeOutputDef = {"phytronBrakeOutput", 6, phytronBrakeOutputArgs}; - -static void phytronCreateControllerCallFunc(const iocshArgBuf *args) -{ - phytronCreateController(args[0].sval, args[1].sval, args[2].ival, args[3].ival, args[4].dval,args[5].ival); -} - -static void phytronCreateAxisCallFunc(const iocshArgBuf *args) -{ - phytronCreateAxis(args[0].sval, args[1].ival, args[2].ival); -} - -static void phytronBrakeOutputCallFunc(const iocshArgBuf *args) -{ - phytronBrakeOutput(args[0].sval, args[1].dval, args[2].dval, args[3].ival, args[4].dval, args[5].dval); -} - -static void phytronRegister(void) -{ - iocshRegister(&phytronCreateControllerDef, phytronCreateControllerCallFunc); - iocshRegister(&phytronCreateAxisDef, phytronCreateAxisCallFunc); - iocshRegister(&phytronBrakeOutputDef, phytronBrakeOutputCallFunc); -} - -extern "C" { -epicsExportRegistrar(phytronRegister); -} diff --git a/phytronApp/src/phytronAxisMotor.h b/phytronApp/src/phytronAxisMotor.h index c4b82f6..d743820 100644 --- a/phytronApp/src/phytronAxisMotor.h +++ b/phytronApp/src/phytronAxisMotor.h @@ -1,11 +1,12 @@ /* +SPDX-License-Identifier: EPICS FILENAME... phytronAxisMotor.h USAGE... Motor record support for Phytron Axis controller. Tom Slejko & Bor Marolt Cosylab d.d. 2014 -Lutz Rossa, Helmholtz-Zentrum Berlin fuer Materialien und Energy GmbH, 2021-2023 +Lutz Rossa, Helmholtz-Zentrum Berlin fuer Materialien und Energy GmbH, 2021-2025 */ @@ -17,8 +18,8 @@ Lutz Rossa, Helmholtz-Zentrum Berlin fuer Materialien und Energy GmbH, 2021-2023 //Number of controller specific parameters #define NUM_PHYTRON_PARAMS 33 -#define MAX_VELOCITY 40000 //steps/s -#define MIN_VELOCITY 1 //steps/s +#define MAX_VELOCITY 500000 //steps/s +#define MIN_VELOCITY 1 //steps/s #define MAX_ACCELERATION 500000 // steps/s^2 #define MIN_ACCELERATION 4000 // steps/s^2 @@ -59,6 +60,9 @@ Lutz Rossa, Helmholtz-Zentrum Berlin fuer Materialien und Energy GmbH, 2021-2023 #define axisDisableMotorString "AXIS_DISABLE_MOTOR" #define axisBrakeEngageTimeString "AXIS_BRAKE_ENGAGE_TIME" #define axisBrakeReleaseTimeString "AXIS_BRAKE_RELEASE_TIME" +#define directCommandString "DIRECT_COMMAND" +#define directReplyString "DIRECT_REPLY" +#define directStatusString "DIRECT_STATUS" typedef enum { phytronSuccess, @@ -94,29 +98,35 @@ enum pollMethod{ pollMethodControllerParallel = 2 /// controller-parallel: combine up to 32 axes a 3 commands/replies into one string }; +class phytronController; +class phytronIO; class phytronAxis : public asynMotorAxis { public: - /* These are the methods we override from the base class */ - phytronAxis(class phytronController *pC, int axis); + // common asyn functions + phytronAxis(class phytronController *pC, int iAxisNo); void report(FILE *fp, int level); + + // motor record functions asynStatus move(double position, int relative, double min_velocity, double max_velocity, double acceleration); asynStatus moveVelocity(double min_velocity, double max_velocity, double acceleration); asynStatus home(double min_velocity, double max_velocity, double acceleration, int forwards); asynStatus stop(double acceleration); asynStatus poll(bool *moving); asynStatus setPosition(double position); - asynStatus setEncoderRatio(double ratio); asynStatus setEncoderPosition(double position); + // phytron axis functions + static int phytronCreateAxis(const char* szControllerName, int iModule, int iAxis); + static int phytronBrakeOutput(const char* szControllerName, float fAxis, float fOutput, int bDisableMotor, double dEngageTime, double dReleaseTime); asynStatus configureBrake(float fOutput, bool bDisableMotor, double dEngageTime, double dReleaseTime); - char axisModuleNo_[5]; //Used by sprintf to form commands - float brakeOutput_; //. of digital output to drive for brake (or -1) - int disableMotor_; //bit0: 0=keep motor enabled, 1=disable idle motor/enable when moved, bit1: 0=disabled, 1=enabled - float brakeEngageTime_; //time to engage brake (disable motor after this time in milliseconds) - float brakeReleaseTime_; //time to release brake (start move after this time in milliseconds) + char axisModuleNo_[5]; ///. of digital output to drive for brake (or -1) + int disableMotor_; /// &asCommands) const; @@ -126,16 +136,16 @@ class phytronAxis : public asynMotorAxis phytronController *pC_; /**< Pointer to the asynMotorController to which this axis belongs. * Abbreviated because it is used very frequently */ + phytronStatus clearAxisError(std::vector* pCmdList); phytronStatus setVelocity(std::vector* pCmdList, double minVelocity, double maxVelocity, int moveType); phytronStatus setAcceleration(std::vector* pCmdList, double acceleration, int movementType); phytronStatus setBrakeOutput(std::vector* pCmdList, bool bWantToMoveMotor); - phytronStatus lastStatus; - int brakeReleased_; - enum pollMethod iPollMethod_; // individual poll method for this axis - - // Workaround for homing type limit - int homeState_; + phytronStatus lastStatus_; ///< last Phytron status + int brakeReleased_; ///< state of brake + enum pollMethod iPollMethod_; ///< individual poll method for this axis + int homeState_; ///< state machine for work around homing to limit switches + float statusResetTime_; ///< error state flag for SEC command: 0=off, <0: configured but not needed, >0: need status reset; 0s asCommands, std::vector &asResponses, bool bAllowNAK = false, bool bForceSingle = false); - void resetAxisEncoderRatio(); //casts phytronStatus to asynStatus - asynStatus phyToAsyn(phytronStatus phyStatus); + static asynStatus phyToAsyn(phytronStatus phyStatus); + static std::string escapeC(std::string sIn); char * controllerName_; - std::vector axes; + std::vector axes_; + std::vector IOs_; + enum TYPE iCtrlType_; ///< controller type protected: //Additional parameters used by additional records @@ -203,14 +223,26 @@ class phytronController : public asynMotorController int axisDisableMotor_; int axisBrakeEngageTime_; int axisBrakeReleaseTime_; + int directCommand_; + int directReply_; + int directStatus_; private: - double timeout_; - phytronStatus lastStatus; - bool do_initial_readout_; - enum pollMethod iDefaultPollMethod_; // default poll method for every axis + static std::vector controllers_; ///< list of all phytron controllers + int iAddress_; ///< serial address of controller + double timeout_; ///< communication timeout + phytronStatus lastStatus_; ///< last communication status + bool do_initial_readout_; ///< helper for initIOC hook to wait for first poll + enum pollMethod iDefaultPollMethod_; ///< default poll method for every axis + bool fake_homed_enable_; ///< enable fake HOMED bits with help Phytron registers 1001...1020 (max. 20 module positions) + epicsUInt16 fake_homed_cache_[20]; ///< cached value, which is updated with every poll (max. 20 module positions) + bool allow_exit_on_error_; ///< allow exit(1) on error + std::string sLastSUI_; ///< last response to SUI command (MCC only) + std::string sCtrlType_; ///< controller type + float statusResetTime_; ///< error state flag for SEC command: 0=off, <0: configured but not needed, >0: need status reset; 0s +#include +#include +#include "phytronIOctrl.h" +#include "phytronAxisMotor.h" + +//****************************************************************************** +// I/O CONTROLLER IMPLEMENTATION +//****************************************************************************** + +//static size of fixed array +#define ARRAY_SIZE(x) ((int)(sizeof(x) / sizeof((x)[0]))) + +struct phytronIOparam +{ + bool bWriteable; ///< read only parameter cannot be written + bool bMCC1only; ///< parameter for MCC-1 only + asynParamType iAsynType; ///< asyn type of parameter type + const char* szAsynName; ///< asyn parameter name +}; + +static struct phytronIOparam g_aParameters[] = +{ + { false, false, asynParamInt32, "IN" }, + { true, false, asynParamInt32, "OUT" }, + { true, true, asynParamInt32, "DIR" }, +}; + +/** + * \brief phytronIO::phytronIO + * \param[in] szIOPortName The name of the asyn port that will be created for this driver. + * \param[in] pCtrl Pointer to existing Phytron controller. + * \param[in] iType Card type. + * \param[in] byCardNr I/O card number (starting with 1). + * \param[in] byChannel channel number (starting with 1). + * \param[in] byDIn Initial digital inputs state. + * \param[in] byDOut Initial digital outputs state. + */ +phytronIO::phytronIO(const char* szIOPortName, phytronController* pCtrl, phytronIO::IOTYPE iType, epicsUInt8 byCardNr, epicsUInt8 byChannel, epicsInt32 iIn, epicsInt32 iOut) + : asynPortDriver(szIOPortName, + 1, // maximum address +#if ASYN_VERSION < 4 || (ASYN_VERSION == 4 && ASYN_REVISION < 32) + ARRAY_SIZE(g_aParameters), +#endif + asynInt32Mask | asynDrvUserMask, // additional interfaces + asynInt32Mask, // additional callback interfaces + ASYN_CANBLOCK, // asynFlags + 1, // autoConnect + 0, // default priority + 0) // default stackSize + , pCtrl_(pCtrl) + , iType_(iType) + , byCardNr_(byCardNr) + , byChannel_(byChannel) + , iIn_(iIn) + , iOut_(iOut) + , iInReason_(-1) + , iOutReason_(-1) + , iDirReason_(-1) +{ + for (size_t i = 0; i < ARRAY_SIZE(g_aParameters); ++i) + { + struct phytronIOparam* p(&g_aParameters[i]); + int iReason(-1); + if (p->bMCC1only) + { + if (pCtrl->iCtrlType_ != phytronController::TYPE_MCC) + continue; + if (epicsStrnCaseCmp(pCtrl->sCtrlType_.c_str(), "MCC-1", 5) != 0) + continue; + } + if (createParam(p->szAsynName, p->iAsynType, &iReason) == asynSuccess && iReason >= 0) + { + m_mapParameters[iReason] = p; + if (strcmp(p->szAsynName, "IN") == 0) + { + iInReason_ = iReason; + setIntegerParam(iReason, iIn); + } + else if (strcmp(p->szAsynName, "OUT") == 0) + { + iOutReason_ = iReason; + setIntegerParam(iReason, iOut); + } + } + } + pCtrl->lock(); + pCtrl->IOs_.push_back(this); + pCtrl->unlock(); +} + +/** + * \brief create a new I/O controller phytronController::createIOcontroller + * \param[in] szPhytronPortName The name of the asyn port that will be created for this driver. + * \param[in] szAsynPortName The name of the asyn port for the existing Phytron controller. + * \param[in] bAnalog Card type. + * \param[in] byCardNr I/O card number (starting with 1). + * \param[in] byChannel channel number (starting with 1). + * \param[in] szInitCommands Initial commands to send or empty. + * \return asynSuccess or asynError + */ +int phytronIO::createIOcontroller(const char* szAsynPortName, const char* szPhytronPortName, bool bAnalog, epicsUInt8 byCardNr, epicsUInt8 byChannel, const char* szInitCommands) +{ + uint32_t i; + phytronIO::IOTYPE iType(bAnalog ? TYPE_GUESS_A : TYPE_GUESS_D); + if (!szAsynPortName || !*szAsynPortName || !szPhytronPortName || !*szPhytronPortName) + goto failed; + for (i = 0; i < phytronController::controllers_.size(); ++i) + { + phytronController* pC(phytronController::controllers_[i]); + if (!pC || strcmp(pC->controllerName_, szPhytronPortName) != 0) + continue; + pC->lock(); + std::vector asCommands, asReplies; + epicsInt32 iIn(0), iOut(0); + bool bOk(false); + pollIO(asCommands, true, pC, iType, byCardNr, byChannel, iIn, iOut); + if (szInitCommands && *szInitCommands) + asCommands.push_back(szInitCommands); + if (pC->sendPhytronMultiCommand(asCommands, asReplies, true, pC->iCtrlType_ != phytronController::TYPE_PHYMOTION) == phytronSuccess) + bOk = pollIO(asReplies, false, pC, iType, byCardNr, byChannel, iIn, iOut); + pC->unlock(); + if (bOk) + { + new phytronIO(szAsynPortName, pC, iType, byCardNr,byChannel, iIn, iOut); + return asynSuccess; + } + } + +failed: + printf("ERROR: invalid call to phytronCreateIOcontroller\n"); + return asynError; +} + +/** + * \brief Called when asyn clients call pasynInt32->read(). + * If this is parameter registered here, it will read the hardware. In other cases, + * this will call the base class, which simply returns the stored value. + * \param[in] pasynUser pasynUser structure that encodes the reason and address. + * \param[out] piValue address of the value to read. + * \return asyn result code + */ +asynStatus phytronIO::readInt32(asynUser* pasynUser, epicsInt32* piValue) +{ + asynStatus iResult(asynError); + struct phytronIOparam* pParam(nullptr); + if (pasynUser->reason >= 0 && m_mapParameters.count(pasynUser->reason)) + pParam = m_mapParameters[pasynUser->reason]; + if (!pParam) // default handler for other asyn parameters + iResult = asynPortDriver::readInt32(pasynUser, piValue); + else if (pParam->iAsynType == asynParamInt32) + { + if (pParam->bWriteable) + *piValue = iOut_; + else + *piValue = iIn_; + iResult = asynSuccess; + } + else + iResult = asynError; + return iResult; +} + +asynStatus phytronIO::writeInt32(asynUser* pasynUser, epicsInt32 iValue) +{ + asynStatus iResult(asynSuccess); + struct phytronIOparam* pParam(nullptr); + if (pasynUser->reason >= 0 && m_mapParameters.count(pasynUser->reason)) + pParam = m_mapParameters[pasynUser->reason]; + if (!pParam) // default handler for other asyn parameters + goto handlewrite; + if (!pParam->bWriteable || pParam->iAsynType != asynParamInt32) + return asynError; + + char szCmd[32]; + szCmd[0] = '\0'; + if (pCtrl_->iCtrlType_ == phytronController::TYPE_MCC) + { + switch (iType_) + { + case TYPE_DO: + case TYPE_DIO: + snprintf(szCmd, ARRAY_SIZE(szCmd), "%s%uS%d%d%d%d%d%d%d%d", + (pasynUser->reason == iDirReason_) ? "EAS" : "AG", + byChannel_, + (iValue & 1) ? 1 : 0, + (iValue & 2) ? 1 : 0, + (iValue & 4) ? 1 : 0, + (iValue & 8) ? 1 : 0, + (iValue & 16) ? 1 : 0, + (iValue & 32) ? 1 : 0, + (iValue & 64) ? 1 : 0, + (iValue & 128) ? 1 : 0); + break; + default: return asynError; + } + } + else + { + switch (iType_) + { + case TYPE_DO: + case TYPE_DIO: + snprintf(szCmd, ARRAY_SIZE(szCmd), "AG%u=%d", byCardNr_, iValue); + break; + case TYPE_AO: + case TYPE_AIO: + snprintf(szCmd, ARRAY_SIZE(szCmd), "DA%u.%u=%d", byCardNr_, byChannel_, iValue); + break; + default: return asynError; + } + } + if (szCmd[0]) + { + szCmd[sizeof(szCmd) - 1] = '\0'; + iResult = phytronController::phyToAsyn(pCtrl_->sendPhytronCommand(const_cast(&szCmd[0]))); + if (iResult == asynSuccess) + iOut_ = iValue; + } + else + iResult = asynError; + +handlewrite: + if (iResult == asynSuccess) + iResult = asynPortDriver::writeInt32(pasynUser, iValue); + return iResult; +} + +void phytronIO::report(FILE* pFile, int iLevel) +{ + epicsInt32 iMaxModule(-1); + asynPortDriver::report(pFile, iLevel); + const char* szType(nullptr); + // all report levels: for this module only - controller type, cardnr, channel + switch (iType_) + { + case TYPE_GUESS_D: szType = "guess digital"; break; + case TYPE_GUESS_A: szType = "guess analog"; break; + case TYPE_DI: szType = "DIN"; break; + case TYPE_DO: szType = "DOUT"; break; + case TYPE_DIO: szType = "DIO"; break; + case TYPE_AI: szType = "AIN"; break; + case TYPE_AO: szType = "AOUT"; break; + case TYPE_AIO: szType = "AIO"; break; + default: szType = "?"; break; + } + fprintf(pFile, "%s %s: card=%u channel=%u in=%d out=%d\n", + pCtrl_->sCtrlType_.c_str(), szType, byCardNr_, byChannel_, iIn_, iOut_); + if (iLevel >= 1) + { + std::string sTmp; + char* pUnits(nullptr); + if (pCtrl_->sendPhytronCommand(std::string((pCtrl_->iCtrlType_ == phytronController::TYPE_MCC) ? "IAR" : "IV"), sTmp) == phytronSuccess) + if (epicsParseInt32(sTmp.c_str(), &iMaxModule, 0, &pUnits) != 0) + iMaxModule = -1; + if (iMaxModule < 1) + iMaxModule = (pCtrl_->iCtrlType_ == phytronController::TYPE_MCC) ? 8 : 21; + } + if (iLevel == 1) + { + // report level 1: detailled controller type (MCC-1, MCC-2, MCM-01/02/03/04, short module list) + fprintf(pFile, " controller: %s\n", pCtrl_->sCtrlType_.c_str()); + if (pCtrl_->iCtrlType_ != phytronController::TYPE_MCC) + { + pCtrl_->lock(); + for (int i = 1; i <= iMaxModule; ++i) + { + std::string sType; + phytronStatus iResult(pCtrl_->sendPhytronCommand(std::string("IM") + std::to_string(i), sType)); + if (iResult == phytronSuccess) + fprintf(pFile, " module %d: %s\n", i, phytronController::escapeC(sType).c_str()); + } + pCtrl_->unlock(); + } + } + else if (iLevel >= 2) + { + // report level 2: detailled version info of all + pCtrl_->lock(); + if (pCtrl_->iCtrlType_ == phytronController::TYPE_MCC) + { + std::string sTmp; + pCtrl_->sendPhytronCommand(std::string("IVR"), sTmp); // minilog version + fprintf(pFile, " controller: %s %s\n", pCtrl_->sCtrlType_.c_str(), phytronController::escapeC(sTmp).c_str()); + for (int i = 1; i <= iMaxModule; ++i) + { + std::string sMod; + if (pCtrl_->sendPhytronCommand(std::to_string(i) + std::string("VI"), sTmp) == phytronSuccess) // indexer version + { + if (!sMod.empty()) + sMod.append(", "); + sMod.append(sTmp); + } + if (pCtrl_->sendPhytronCommand(std::to_string(i) + std::string("VL"), sTmp) == phytronSuccess) // loader version + { + if (!sMod.empty()) + sMod.append(", "); + sMod.append(sTmp); + } + if (sMod.empty()) + sMod = "no version info"; + fprintf(pFile, " axis %d: %s\n", i, phytronController::escapeC(sMod).c_str()); + } + } + else + { + for (int i = 0; i <= iMaxModule; ++i) + { + std::string sType; + phytronStatus iResult(pCtrl_->sendPhytronCommand(std::string("IV") + std::to_string(i), sType)); + if (!i) + fprintf(pFile, " controller: %s\n", phytronController::escapeC(sType).c_str()); + else if (iResult == phytronSuccess) + fprintf(pFile, " module %d: %s\n", i, phytronController::escapeC(sType).c_str()); + } + } + pCtrl_->unlock(); + } +} + +/** + * \brief called by phytronController::poll cycle + * \param[in,out] asCmdReplyList bIsRequest=true: append command to command list, bIsRequest=false: parse and remove 1st reply from replies + * \param[in] bIsRequest type of call + */ +bool phytronIO::pollIO(std::vector& asCmdReplyList, bool bIsRequest) +{ + bool bOk(pollIO(asCmdReplyList, bIsRequest, pCtrl_, iType_, byCardNr_, byChannel_, iIn_, iOut_)); + if (bOk) + { + setIntegerParam(iInReason_, iIn_); + setIntegerParam(iOutReason_, iOut_); + callParamCallbacks(); + } + return bOk; +} + +/** + * \brief called by phytronController::poll cycle or by createIOcontroller + * \param[in,out] asCmdReplyList bIsRequest=true: append command to command list, bIsRequest=false: parse and remove 1st reply from replies + * \param[in] bIsRequest type of call + * \param[in] pCtrl pointer to controller + * \param[in,out] iType card type (change TYPE_GUESS_D or TYPE_GUESS_A to appropriate) + * \param[in] byCardNr card number (starting with 1) + * \param[in] byChannel channel number (starting with 1) + * \param[out] iIn value of input(s) + * \param[out] iOut value of output(s) + */ +bool phytronIO::pollIO(std::vector& asCmdReplyList, bool bIsRequest, phytronController* pCtrl, IOTYPE &iType, epicsUInt8 byCardNr, epicsUInt8 byChannel, epicsInt32& iIn, epicsInt32& iOut) +{ + std::string sVal; + bool bMCCbinary(false); + if (pCtrl->iCtrlType_ == phytronController::TYPE_MCC) + { + if (byCardNr > 1) + return false; + switch (iType) // MCC-1/MCC-2 has fixed I/O + { + case TYPE_GUESS_D: iType = TYPE_DIO; bMCCbinary = true; break; + case TYPE_GUESS_A: iType = TYPE_AI; break; + case TYPE_DIO: bMCCbinary = true; break; + default: break; + } + } + if (bIsRequest) + { + // generate requests + switch (iType) + { + case TYPE_GUESS_D: + case TYPE_DIO: + asCmdReplyList.push_back(std::string("AG") + std::to_string(byCardNr) + "R"); + asCmdReplyList.push_back(std::string("EG") + std::to_string(byCardNr) + "R"); + break; + case TYPE_GUESS_A: + case TYPE_AIO: + asCmdReplyList.push_back(std::string("DA") + std::to_string(byCardNr) + "." + std::to_string(byChannel)); + asCmdReplyList.push_back(std::string("AD") + std::to_string(byCardNr) + "." + std::to_string(byChannel)); + break; + case TYPE_DI: + asCmdReplyList.push_back(std::string("EG") + std::to_string(byCardNr) + "R"); + break; + case TYPE_DO: + asCmdReplyList.push_back(std::string("AG") + std::to_string(byCardNr) + "R"); + break; + case TYPE_AI: + if (pCtrl->iCtrlType_ == phytronController::TYPE_MCC) + asCmdReplyList.push_back(std::string("AD") + std::to_string(byChannel) + "R"); + else + asCmdReplyList.push_back(std::string("AD") + std::to_string(byCardNr) + "." + std::to_string(byChannel)); + break; + case TYPE_AO: + asCmdReplyList.push_back(std::string("DA") + std::to_string(byCardNr) + "." + std::to_string(byChannel)); + break; + default: + return false; + } + return true; + } + switch (iType) // check reply count + { + case TYPE_GUESS_D: + case TYPE_GUESS_A: + case TYPE_DIO: + case TYPE_AIO: + if (asCmdReplyList.size() < 2) // not enough values + return false; + break; + case TYPE_DI: + case TYPE_DO: + case TYPE_AI: + case TYPE_AO: + if (asCmdReplyList.size() < 1) // not enough values + return false; + break; + default: + break; + } + switch (iType) // check for ACK/NAK and in case of TYPE_GUESS_? set correct type + { + case TYPE_GUESS_D: + if (asCmdReplyList[0].substr(0, 1) == "\x06" && asCmdReplyList[1].substr(0, 1) == "\x06") + iType = TYPE_DIO; + else if (asCmdReplyList[0].substr(0, 1) != "\x06" && asCmdReplyList[1].substr(0, 1) != "\x06") + return false; + else + iType = (asCmdReplyList[0].substr(0, 1) == "\x06") ? TYPE_DO : TYPE_DI; + break; + case TYPE_GUESS_A: + if (asCmdReplyList[0].substr(0, 1) == "\x06" && asCmdReplyList[1].substr(0, 1) == "\x06") + iType = TYPE_AIO; + else if (asCmdReplyList[0].substr(0, 1) != "\x06" && asCmdReplyList[1].substr(0, 1) != "\x06") + return false; + else + iType = (asCmdReplyList[0].substr(0, 1) == "\x06") ? TYPE_AO : TYPE_AI; + break; + + case TYPE_DI: + case TYPE_DO: + case TYPE_AI: + case TYPE_AO: + if (asCmdReplyList[0].substr(0, 1) != "\x06") // no ACK + return false; + break; + default: + if (asCmdReplyList[0].substr(0, 1) != "\x06" || asCmdReplyList[1].substr(0, 1) != "\x06") // no ACK + return false; + break; + } + switch (iType) // handle output value + { + case TYPE_DIO: + case TYPE_DO: + case TYPE_AIO: + case TYPE_AO: + sVal = asCmdReplyList[0]; + sVal.erase(0, 1); + if (bMCCbinary) + { + epicsInt32 iBit(1); + iOut = 0; + while (!sVal.empty()) + { + switch (sVal.front()) + { + case '0': break; + case '1': iOut |= iBit; break; + default: return false; + } + iBit <<= 1; + sVal.erase(0, 1); + } + } + else if (epicsParseInt32(sVal.c_str(), &iOut, 0, nullptr) != 0) + return false; + asCmdReplyList.erase(asCmdReplyList.begin(), asCmdReplyList.begin() + 1); + break; + default: + break; + } + switch (iType) // handle input value + { + case TYPE_DIO: + case TYPE_DI: + case TYPE_AIO: + case TYPE_AI: + sVal = asCmdReplyList[0]; + sVal.erase(0, 1); + if (bMCCbinary) + { + epicsInt32 iBit(1); + iIn = 0; + while (!sVal.empty()) + { + switch (sVal.front()) + { + case '0': break; + case '1': iIn |= iBit; break; + default: return false; + } + iBit <<= 1; + sVal.erase(0, 1); + } + } + else if (epicsParseInt32(sVal.c_str(), &iIn, 0, nullptr) != 0) + return false; + asCmdReplyList.erase(asCmdReplyList.begin(), asCmdReplyList.begin() + 1); + break; + default: + break; + } + return true; +} diff --git a/phytronApp/src/phytronIOctrl.h b/phytronApp/src/phytronIOctrl.h new file mode 100644 index 0000000..d63af21 --- /dev/null +++ b/phytronApp/src/phytronIOctrl.h @@ -0,0 +1,46 @@ +/* +SPDX-License-Identifier: EPICS +FILENAME... phytronIOctrl.h +USAGE... I/O support for Phytron controller. + +Bernhard Kuner, Helmholtz-Zentrum Berlin fuer Materialien und Energy GmbH, 2022-2023 +Lutz Rossa, Helmholtz-Zentrum Berlin fuer Materialien und Energy GmbH, 2024-2025 + +*/ +#include +#include + +struct phytronIOparam; +class phytronController; +class phytronIO : public asynPortDriver +{ +public: + enum IOTYPE { TYPE_GUESS_D, TYPE_GUESS_A, TYPE_DI, TYPE_DO, TYPE_DIO, TYPE_AI, TYPE_AO, TYPE_AIO }; + + // common asyn functions + phytronIO(const char* szIOPortName, phytronController* pCtrl, IOTYPE iType, epicsUInt8 byCardNr, epicsUInt8 byChannel, epicsInt32 iIn, epicsInt32 iOut); + asynStatus readInt32(asynUser* pasynUser, epicsInt32* piValue); + asynStatus writeInt32(asynUser* pasynUser, epicsInt32 iValue); + void report(FILE* pFile, int iLevel); + + // I/O controller functions + static int createIOcontroller(const char* szAsynPortName, const char* szPhytronPortName, bool bAnalog, epicsUInt8 byCardNr, epicsUInt8 byChannel, const char* szInitCommands); + bool pollIO(std::vector& asCmdReplyList, bool bIsRequest); + static bool pollIO(std::vector& asCmdReplyList, bool bIsRequest, phytronController* pCtrl, IOTYPE& iType, epicsUInt8 byCardNr, epicsUInt8 byChannel, epicsInt32& iIn, epicsInt32& iOut); + +protected: + std::map m_mapParameters; ///< mapping of asyn reasons to parameter + phytronController* pCtrl_; ///< controller instance + IOTYPE iType_; ///< card type + epicsUInt8 byCardNr_; ///< I/O card number + epicsUInt8 byChannel_; ///< I/O channel number + epicsInt32 iIn_; ///< last polled input value + epicsInt32 iOut_; ///< last polled output value + int iInReason_; ///< index of IN parameter + int iOutReason_; ///< index of OUT parameter + int iDirReason_; ///< index of DIR parameter + int iStatusReason_; ///< index of STATUS parameter + int iReplyReason_; ///< index of REPLY parameter + +friend class phytronController; +}; diff --git a/phytronApp/src/phytronSupport.cpp b/phytronApp/src/phytronSupport.cpp new file mode 100644 index 0000000..7c38312 --- /dev/null +++ b/phytronApp/src/phytronSupport.cpp @@ -0,0 +1,172 @@ +/* +SPDX-License-Identifier: EPICS +FILENAME... phytronSupport.cpp +USAGE... IOCSH Support for Phytron controller. + +Lutz Rossa, Helmholtz-Zentrum Berlin fuer Materialien und Energy GmbH, 2025 + +*/ +#include +#include +#include "phytronAxisMotor.h" +#include "phytronIOctrl.h" + +//static size of fixed array +#define ARRAY_SIZE(x) ((int)(sizeof(x) / sizeof((x)[0]))) + +//****************************************************************************** +// IOCSH SPECIFIC IMPLEMENTATION +//****************************************************************************** + +/** Parameters for iocsh Phytron axis registration*/ +static const iocshArg phytronCreateAxisArg0 = {"Controller Name", iocshArgString}; +static const iocshArg phytronCreateAxisArg1 = {"Module index", iocshArgInt}; +static const iocshArg phytronCreateAxisArg2 = {"Axis index", iocshArgInt}; +static const iocshArg* const phytronCreateAxisArgs[] = {&phytronCreateAxisArg0, + &phytronCreateAxisArg1, + &phytronCreateAxisArg2}; + +/** Parameters for iocsh Phytron phyMOTION controller registration */ +static const iocshArg phytronCreatePhymotionArg0 = {"Controller name", iocshArgString}; +static const iocshArg phytronCreatePhymotionArg1 = {"Phytron communication port name", iocshArgString}; +static const iocshArg phytronCreatePhymotionArg2 = {"Moving poll period (ms)", iocshArgInt}; +static const iocshArg phytronCreatePhymotionArg3 = {"Idle poll period (ms)", iocshArgInt}; +static const iocshArg phytronCreatePhymotionArg4 = {"Timeout (ms)", iocshArgDouble}; +static const iocshArg phytronCreatePhymotionArg5 = {"Do not restart controller with IOC", iocshArgInt}; +static const iocshArg * const phytronCreatePhymotionArgs[] = {&phytronCreatePhymotionArg0, + &phytronCreatePhymotionArg1, + &phytronCreatePhymotionArg2, + &phytronCreatePhymotionArg3, + &phytronCreatePhymotionArg4, + &phytronCreatePhymotionArg5}; + +/** Parameters for iocsh Phytron MCC controller registration */ +static const iocshArg phytronCreateMCCArg0 = {"Controller name", iocshArgString}; +static const iocshArg phytronCreateMCCArg1 = {"Phytron communication port name", iocshArgString}; +static const iocshArg phytronCreateMCCArg2 = {"Controller address (0..15)", iocshArgInt}; +static const iocshArg phytronCreateMCCArg3 = {"Moving poll period (ms)", iocshArgInt}; +static const iocshArg phytronCreateMCCArg4 = {"Idle poll period (ms)", iocshArgInt}; +static const iocshArg phytronCreateMCCArg5 = {"Timeout (ms)", iocshArgDouble}; +static const iocshArg phytronCreateMCCArg6 = {"Do not restart controller with IOC", iocshArgInt}; +static const iocshArg * const phytronCreateMCCArgs[] = {&phytronCreateMCCArg0, + &phytronCreateMCCArg1, + &phytronCreateMCCArg2, + &phytronCreateMCCArg3, + &phytronCreateMCCArg4, + &phytronCreateMCCArg5, + &phytronCreateMCCArg6}; + + +/** Parameters for iocsh Phytron brake(s) output registration */ +static const iocshArg phytronBrakeOutputArg0 = {"Controller Name", iocshArgString}; +static const iocshArg phytronBrakeOutputArg1 = {"Axis index", iocshArgDouble}; +static const iocshArg phytronBrakeOutputArg2 = {"Output index", iocshArgDouble}; +static const iocshArg phytronBrakeOutputArg3 = {"Motor disable flag", iocshArgInt}; +static const iocshArg phytronBrakeOutputArg4 = {"Brake engage wait time (ms)", iocshArgDouble}; +static const iocshArg phytronBrakeOutputArg5 = {"Brake release wait time (ms)", iocshArgDouble}; +static const iocshArg* const phytronBrakeOutputArgs[] = {&phytronBrakeOutputArg0, + &phytronBrakeOutputArg1, + &phytronBrakeOutputArg2, + &phytronBrakeOutputArg3, + &phytronBrakeOutputArg4, + &phytronBrakeOutputArg5}; + +/** Parameters for iocsh Phytron I/O controller registration */ +static const iocshArg phytronCreateIOArg0 = {"IO Controller Name", iocshArgString}; +static const iocshArg phytronCreateIOArg1 = {"Phytron Controller Name", iocshArgString}; +static const iocshArg phytronCreateIOArg2 = {"card number (1..)", iocshArgInt}; +static const iocshArg phytronCreateIOArg3 = {"channel number (1..)", iocshArgInt}; +static const iocshArg phytronCreateIOArg4 = {"initial commands string", iocshArgString}; +static const iocshArg* const phytronCreateIOArgs[] = {&phytronCreateIOArg0, + &phytronCreateIOArg1, + &phytronCreateIOArg2, + &phytronCreateIOArg3, + &phytronCreateIOArg4}; + +/** IOCSH functions */ +static const iocshFuncDef phytronCreateAxisDef = {"phytronCreateAxis", ARRAY_SIZE(phytronCreateAxisArgs), phytronCreateAxisArgs +#if defined(EPICS_VERSION) +#if EPICS_VERSION > 7 || (EPICS_VERSION == 7 && (EPICS_REVISION > 0 || EPICS_MODIFICATION >= 3)) + , "Create an axis instance for one motor of any previously created Phytron controller.\n" +#endif +#endif +}; +static const iocshFuncDef phytronCreatePhymotionDef = {"phytronCreateController", ARRAY_SIZE(phytronCreatePhymotionArgs), phytronCreatePhymotionArgs +#if defined(EPICS_VERSION) +#if EPICS_VERSION > 7 || (EPICS_VERSION == 7 && (EPICS_REVISION > 0 || EPICS_MODIFICATION >= 3)) + , "Create a Phytron phyMOTION controller instance.\n" +#endif +#endif +}; +static const iocshFuncDef phytronCreateMCCDef = {"phytronCreateMCC", ARRAY_SIZE(phytronCreateMCCArgs), phytronCreateMCCArgs +#if defined(EPICS_VERSION) +#if EPICS_VERSION > 7 || (EPICS_VERSION == 7 && (EPICS_REVISION > 0 || EPICS_MODIFICATION >= 3)) + , "Create a Phytron MCC-1 or MCC-2 controller instance.\n" +#endif +#endif +}; +static const iocshFuncDef phytronBrakeOutputDef = {"phytronBrakeOutput", ARRAY_SIZE(phytronBrakeOutputArgs), phytronBrakeOutputArgs +#if defined(EPICS_VERSION) +#if EPICS_VERSION > 7 || (EPICS_VERSION == 7 && (EPICS_REVISION > 0 || EPICS_MODIFICATION >= 3)) + , "Configure brake support for a Phytron motor axis.\n" +#endif +#endif +}; +static const iocshFuncDef phytronCreateDIODef = {"phytronCreateDigital", ARRAY_SIZE(phytronCreateIOArgs), phytronCreateIOArgs +#if defined(EPICS_VERSION) +#if EPICS_VERSION > 7 || (EPICS_VERSION == 7 && (EPICS_REVISION > 0 || EPICS_MODIFICATION >= 3)) + , "Create a Phytron digital I/O instance for any previously created Phytron controller.\n" +#endif +#endif +}; +static const iocshFuncDef phytronCreateAIODef = {"phytronCreateAnalog", ARRAY_SIZE(phytronCreateIOArgs), phytronCreateIOArgs +#if defined(EPICS_VERSION) +#if EPICS_VERSION > 7 || (EPICS_VERSION == 7 && (EPICS_REVISION > 0 || EPICS_MODIFICATION >= 3)) + , "Create a Phytron analog I/O instance for any previously created Phytron controller.\n" +#endif +#endif +}; + +static void phytronCreatePhymotionCallFunc(const iocshArgBuf *args) +{ + phytronController::phytronCreatePhymotion(args[0].sval, args[1].sval, args[2].ival, args[3].ival, args[4].dval,args[5].ival); +} + +static void phytronCreateMCCCallFunc(const iocshArgBuf *args) +{ + phytronController::phytronCreateMCC(args[0].sval, args[1].sval, args[2].ival, args[3].ival, args[4].ival, args[5].dval, args[6].ival); +} + +static void phytronCreateAxisCallFunc(const iocshArgBuf *args) +{ + phytronAxis::phytronCreateAxis(args[0].sval, args[1].ival, args[2].ival); +} + +static void phytronBrakeOutputCallFunc(const iocshArgBuf *args) +{ + phytronAxis::phytronBrakeOutput(args[0].sval, args[1].dval, args[2].dval, args[3].ival, args[4].dval, args[5].dval); +} + +static void phytronCreateDigitalIOcontrollerCallFunc(const iocshArgBuf *args) +{ + phytronIO::createIOcontroller(args[0].sval, args[1].sval, false, args[2].ival, args[3].ival, args[4].sval); +} + +static void phytronCreateAnalogIOcontrollerCallFunc(const iocshArgBuf *args) +{ + phytronIO::createIOcontroller(args[0].sval, args[1].sval, true, args[2].ival, args[3].ival, args[4].sval); +} + +static void phytronRegister(void) +{ + iocshRegister(&phytronCreatePhymotionDef, phytronCreatePhymotionCallFunc); + iocshRegister(&phytronCreateMCCDef, phytronCreateMCCCallFunc); + iocshRegister(&phytronCreateAxisDef, phytronCreateAxisCallFunc); + iocshRegister(&phytronBrakeOutputDef, phytronBrakeOutputCallFunc); + iocshRegister(&phytronCreateDIODef, phytronCreateDigitalIOcontrollerCallFunc); + iocshRegister(&phytronCreateAIODef, phytronCreateAnalogIOcontrollerCallFunc); +} + +extern "C" { +epicsExportRegistrar(phytronRegister); +} diff --git a/phytronApp/src/phytronSupport.dbd b/phytronApp/src/phytronSupport.dbd index 6cd997e..232236c 100644 --- a/phytronApp/src/phytronSupport.dbd +++ b/phytronApp/src/phytronSupport.dbd @@ -1 +1,2 @@ +# SPDX-License-Identifier: EPICS registrar(phytronRegister)