diff --git a/config/CONFIG b/config/CONFIG new file mode 100644 index 00000000..8e3f377d --- /dev/null +++ b/config/CONFIG @@ -0,0 +1,3 @@ +#CONFIG +# Add any changes to make rules here +CROSS_COMPILER_TARGET_ARCHS = mv167 diff --git a/config/CONFIG_APP b/config/CONFIG_APP new file mode 100644 index 00000000..a4fb3053 --- /dev/null +++ b/config/CONFIG_APP @@ -0,0 +1,20 @@ +#CONFIG_APP +include $(TOP)/config/RELEASE +-include $(TOP)/config/RELEASE.$(HOST_ARCH) +include $(EPICS_BASE)/config/CONFIG +INSTALL_LOCATION = $(TOP) +ifdef INSTALL_LOCATION_APP +INSTALL_LOCATION = $(INSTALL_LOCATION_APP) +endif + +ifdef T_A +-include $(TOP)/config/O.$(T_A)/CONFIG_APP_INCLUDE +endif + +# Where to install databases +INSTALL_DB = $(INSTALL_LOCATION)/db +# dbst based database optimization (default: NO) +DB_OPT = NO +# May be overridden if not all databases should be installed +INSTALLDB = $(DB) +include $(TOP)/config/CONFIG diff --git a/config/Makefile b/config/Makefile new file mode 100644 index 00000000..f170713c --- /dev/null +++ b/config/Makefile @@ -0,0 +1,9 @@ +# +# $Id: Makefile,v 1.1 2000-02-08 22:17:42 sluiter Exp $ +# + +TOP=.. +include $(TOP)/config/CONFIG_APP + +include $(TOP)/config/RULES_ARCHS + diff --git a/config/Makefile.Host b/config/Makefile.Host new file mode 100644 index 00000000..b1df44aa --- /dev/null +++ b/config/Makefile.Host @@ -0,0 +1,20 @@ +# +# $Id: Makefile.Host,v 1.1 2000-02-08 22:17:42 sluiter Exp $ +# + +TOP=../.. +include $(TOP)/config/CONFIG_APP + +TARGETS = CONFIG_APP_INCLUDE + +include $(TOP)/config/RULES.Host + +inc:: $(TARGETS) + +ifeq ($(wildcard $(TOP)/config/RELEASE.$(HOST_ARCH)),$(TOP)/config/RELEASE.$(HOST_ARCH)) +CONFIG_APP_INCLUDE: $(TOP)/config/RELEASE.$(HOST_ARCH) +endif + +CONFIG_APP_INCLUDE: $(TOP)/config/RELEASE $(TOP)/config/CONFIG_APP + $(RM) $@ + @$(PERL) $(TOP)/config/makeConfigAppInclude.pl $(T_A) $@ $(TOP) diff --git a/config/Makefile.Vx b/config/Makefile.Vx new file mode 100644 index 00000000..0c3ea190 --- /dev/null +++ b/config/Makefile.Vx @@ -0,0 +1,20 @@ +# +# $Id: Makefile.Vx,v 1.1 2000-02-08 22:17:43 sluiter Exp $ +# + +TOP=../.. +include $(TOP)/config/CONFIG_APP + +TARGETS = CONFIG_APP_INCLUDE + +include $(TOP)/config/RULES.Vx + +inc:: $(TARGETS) + +ifeq ($(wildcard $(TOP)/config/RELEASE.$(HOST_ARCH)),$(TOP)/config/RELEASE.$(HOST_ARCH)) +CONFIG_APP_INCLUDE: $(TOP)/config/RELEASE.$(HOST_ARCH) +endif + +CONFIG_APP_INCLUDE: $(TOP)/config/RELEASE $(TOP)/config/CONFIG_APP + $(RM) $@ + @$(PERL) $(TOP)/config/makeConfigAppInclude.pl $(T_A) $@ $(TOP) diff --git a/config/RELEASE b/config/RELEASE new file mode 100644 index 00000000..e79a7ad8 --- /dev/null +++ b/config/RELEASE @@ -0,0 +1,14 @@ +# RELEASE - Location of external products +# +# (Architecture dependent declarations should go into RELEASE.) +# +# Note: This file will be scanned to automatically generate +# include path definitions etc. +# The order of the following declarations will be retained +# within those definitions. +# +# Add your declarations here + +SUPPORT=/usr/local/iocapps/R3.13.2/support +EPICS_BASE=$(SUPPORT)/base/3-13-2 +HIDEOS=/usr/local/hideos/v3.1/install diff --git a/config/RULES.Db b/config/RULES.Db new file mode 100644 index 00000000..f6862412 --- /dev/null +++ b/config/RULES.Db @@ -0,0 +1,200 @@ +# $Id: RULES.Db,v 1.1 2000-02-08 22:17:43 sluiter Exp $ +# +# Rules for making things related to databases +# +MAKEBPT = $(EPICS_BASE_HOST_BIN)/makeBpt$(EXE) +DBLOADTEMPLATE = $(EPICS_BASE_HOST_BIN)/dbLoadTemplate$(EXE) +DBEXPAND = $(EPICS_BASE_HOST_BIN)/dbExpand$(EXE) +DBST = dbst +MAKEDBDEPENDS = $(PERL) $(TOP)/config/makeDbDepends.pl +REPLACEVAR = $(PERL) $(TOP)/config/replaceVAR.pl +ifndef WIN32 +TOUCH = touch +else +TOUCH = type NUL >> +endif + +#----------------------------------------------------------------- +# if we are not building base add base dbd dirs + +ifneq ($(EPICS_BASE),$(TOP)) +ifneq ($(EPICS_BASE),$(INSTALL_LOCATION)) +EPICS_DBDFLAGS += -I $(EPICS_BASE)/dbd +endif +endif + +#--------------------------------------------------------------- +# ---------------------------------------------------- +# create names (lists) for installed things +# ---------------------------------------------------- + +INSTALL_BPTS = $(BPTS:%= $(INSTALL_DBD)/%) +INSTALL_DBDS = $(DBDINSTALL:%= $(INSTALL_DBD)/%) +INSTALL_DBDNAME = $(DBDNAME:%= $(INSTALL_DBD)/%) +INSTALL_DATA = $(INSTALLDB:%=$(INSTALL_DB)/%) +INSTALL_TEMPLATES = $(filter %.template,$(INSTALL_DATA)) + +#--------------------------------------------------------------- +# Main targets + +all:: install + +inc:: $(INSTALL_DBDS) $(INSTALL_BPTS) $(INSTALL_TEMPLATES) + +rebuild:: clean install + +install:: inc buildInstall + +buildInstall:: build $(INSTALL_DATA) + +clean:: + @echo "Cleaning" + @$(RM) $(DB) $(DBDNAME) *.template *.substitutions *.db.raw \ + *.db-stamp *.edf esiread.cnf + +##################################################### "Foreign" templates + +TEMPLATE_LINKS = $(filter-out $(notdir $(USES_TEMPLATE)), $(USES_TEMPLATE)) +TEMPLATE_FILES = $(filter $(notdir $(USES_TEMPLATE)), $(USES_TEMPLATE)) +DB_STAMP = $(patsubst %.db, %.db-stamp, $(DB)) +DB_REALTARGET = $(patsubst %.db-stamp, %.db, $@) + +ifneq '$(TEMPLATE_LINKS)' '' +build:: $(notdir $(TEMPLATE_LINKS)) +endif +build:: $(INSTALL_DBDNAME) $(TEMPLATE_FILES) $(DB_STAMP) + +$(notdir $(TEMPLATE_LINKS)): %.template: +ifndef WIN32 + @$(RM) $(notdir $(TEMPLATE_LINKS)) + ln -s $(TEMPLATE_LINKS) . +# Workaround for dbLoadTemplate bug: terminate here if link target doesn't exist + @cat $(TEMPLATE_LINKS) > /dev/null +else + @$(RM) $(notdir $(TEMPLATE_LINKS)) + $(CP) $(TEMPLATE_LINKS) . +endif + +##################################################### Inflated or plain databases + +$(INSTALL_DB)/%.db: %.db-stamp + @echo "Installing database $@" + @$(INSTALL) -d -m 644 $(patsubst %.db-stamp, %.db, $<) $(@D) + +# Must have DBDNAME defined to use dbst optimization +ifndef DBDNAME +DB_OPT = NO +endif + +# dbst based database optimization +ifeq '$(DB_OPT)' 'YES' +.PRECIOUS: %.db.raw +%.db-stamp: %.db.raw $(INSTALL_DBD)/$(DBDNAME) + @echo "Optimizing database $@" + $(DBST) $(INSTALL_DBD)/$(DBDNAME) $< -d > $(DB_REALTARGET) + @$(TOUCH) $@ +%.db-stamp: %.t.db.raw $(INSTALL_DBD)/$(DBDNAME) + @echo "Optimizing database $@" + $(DBST) $(INSTALL_DBD)/$(DBDNAME) $< -d > $(DB_REALTARGET) + @$(TOUCH) $@ +else +# NO optimization => move it and keep a stamp +%.db-stamp: %.db.raw + @$(MV) $< $(DB_REALTARGET) + @$(TOUCH) $@ + @$(TOUCH) $< +%.db-stamp: %.t.db.raw + @$(MV) $< $(DB_REALTARGET) + @$(TOUCH) $@ + @$(TOUCH) $< +endif + +%.t.db.raw: %.substitutions + @echo "Inflating database from $<" + @$(RM) $@ + @$(DBLOADTEMPLATE) $< > $@ + +##################################################### CapFast filter + +%.edf:: ../%.sch $(DEPSCHS) + @if [ ! -f cad.rc -a -r ../cad.rc ] ; then ln -s ../cad.rc ; fi + $(SCH2EDIF) $(SCH2EDIF_SYSFLAGS) $(SCH2EDIF_FLAGS) $< + +##################################################### Substitution files + +$(INSTALL_DB)/%.substitutions: %.substitutions + @echo "Installing $@" + @$(INSTALL) -d -m 644 $(@F) $(@D) + +%.substitutions:: ../%.substitutions + @$(CP) $< $@ + +ifdef CREATESUBSTITUTIONS +%.substitutions:: $(word $(words $(CREATESUBSTITUTIONS)),$(CREATESUBSTITUTIONS)) + @$(CREATESUBSTITUTIONS) $* +endif + +# Better make it PRECIOUS (to get around make bug) +.PRECIOUS: %.substitutions + +##################################################### Template databases + +# Installed template files (dbLoadTemplate() on IOC side) +$(INSTALL_DB)/%.template: %.template + @echo "Installing $@" + @$(INSTALL) -d -m 644 $(@F) $(@D) + +%.template:: ../%.template + @$(CP) $< $@ + +%.template: %.edf + $(E2DB) $(E2DB_SYSFLAGS) $(E2DB_FLAGS) -n $@.VAR $< + @$(REPLACEVAR) < $@.VAR > $@ + @$(RM) $@.VAR + +##################################################### Flat databases + +%.db.raw:: ../%.db + $(CP) $< $@ + +%.db.raw: %.edf + $(E2DB) $(E2DB_SYSFLAGS) $(E2DB_FLAGS) -n $@.VAR $< + @$(REPLACEVAR) < $@.VAR > $@ + @$(RM) $@.VAR + +##################################################### DBD stuff + +$(INSTALL_DBD)/%: % + @echo "Installing $@" + @$(INSTALL) -d -m 644 $< $(@D) + +$(INSTALL_DBD)/%:: ../% + @echo "Installing $@" + @$(INSTALL) -d -m 644 $< $(@D) + +bpt%.dbd: bpt%.data + $(RM) $@ + $(MAKEBPT) $< + +bpt%.dbd:: ../bpt%.data + $(RM) $@ + $(MAKEBPT) $< + +# Patch for old applications +ifdef USER_DBDFLAGS +DBDFLAGS = $(USER_DBDFLAGS) +endif + +ifdef DBDEXPAND +$(DBDNAME): ../$(DBDEXPAND) + @echo "Expanding dbd" + @$(RM) $@ + $(DBEXPAND) $(DBDFLAGS) $< > $@ +endif + +##################################################### Dependencies + +DEPENDS: $(filter $(patsubst %.db, %.substitutions, $(DB)), $(wildcard *.substitutions)) + @$(MAKEDBDEPENDS) $^ + +-include DEPENDS diff --git a/config/RULES.Host b/config/RULES.Host new file mode 100644 index 00000000..ffbb575f --- /dev/null +++ b/config/RULES.Host @@ -0,0 +1,3 @@ +#RULES.Host + +include $(EPICS_BASE)/config/RULES.Host diff --git a/config/RULES.Vx b/config/RULES.Vx new file mode 100644 index 00000000..308a2ef7 --- /dev/null +++ b/config/RULES.Vx @@ -0,0 +1,9 @@ +#RULES.Vx +include $(EPICS_BASE)/config/RULES.Vx +inc:: $(INSTALL_INCREC) +# +# turn off implicit rules search for all binaries in base +# +#Jeff says this turned off all dependency checking +#????? What to do???? +#$(EPICS_BASE_BIN)/* : ; diff --git a/config/RULES.ioc b/config/RULES.ioc new file mode 100644 index 00000000..c8a3c0a8 --- /dev/null +++ b/config/RULES.ioc @@ -0,0 +1,9 @@ +#RULES.ioc +include $(EPICS_BASE)/config/RULES_DIRS +buildInstall:: cdCommands + +cdCommands: Makefile + $(PERL) $(TOP)/config/makeIocCdCommands.pl $(ARCH) + +clean:: + @$(RM) cdCommands diff --git a/config/RULES.iocBoot b/config/RULES.iocBoot new file mode 100644 index 00000000..60988fd0 --- /dev/null +++ b/config/RULES.iocBoot @@ -0,0 +1,4 @@ +#RULES.iocBoot +DIRS += $(wildcard ioc*) +DIRS += $(wildcard as*) +include $(EPICS_BASE)/config/RULES_DIRS diff --git a/config/RULES_ARCHS b/config/RULES_ARCHS new file mode 100644 index 00000000..812c5c2e --- /dev/null +++ b/config/RULES_ARCHS @@ -0,0 +1,2 @@ +#RULES_ARCHS +include $(EPICS_BASE)/config/RULES_ARCHS diff --git a/config/RULES_DIRS b/config/RULES_DIRS new file mode 100644 index 00000000..1e1c54ed --- /dev/null +++ b/config/RULES_DIRS @@ -0,0 +1,2 @@ +#RULES_DIRS +include $(EPICS_BASE)/config/RULES_DIRS diff --git a/config/RULES_TOP b/config/RULES_TOP new file mode 100644 index 00000000..d23bc9ca --- /dev/null +++ b/config/RULES_TOP @@ -0,0 +1,5 @@ +#RULES_TOP +include $(EPICS_BASE)/config/RULES_TOP + +uninstall:: + @$(RMDIR) $(INSTALL_DB) diff --git a/config/makeConfigAppInclude.pl b/config/makeConfigAppInclude.pl new file mode 100644 index 00000000..10ddbd15 --- /dev/null +++ b/config/makeConfigAppInclude.pl @@ -0,0 +1,62 @@ +# $Id: makeConfigAppInclude.pl,v 1.1 2000-02-08 22:17:45 sluiter Exp $ + +eval 'exec perl -S $0 ${1+"$@"}' # -*- Mode: perl -*- + if $running_under_some_shell; # makeConfigAppInclude.pl + +use Cwd; + +$arch = $ARGV[0]; +$outfile = $ARGV[1]; +$top = $ARGV[2]; + +unlink("${outfile}"); +open(OUT,">${outfile}") or die "$! opening ${outfile}"; +print OUT "#Do not modify this file.\n"; +print OUT "#This file is created during the build.\n"; + +@files =(); +push(@files,"$top/config/RELEASE"); +push(@files,"$top/config/RELEASE.${arch}"); +foreach $file (@files) { + if (-r "$file") { + open(IN, "$file") or die "Cannot open $file\n"; + while ($line = ) { + next if ( $line =~ /\s*#/ ); + chomp($line); + $_ = $line; + #the following looks for + # prefix = $(macro)post + ($prefix,$macro,$post) = /(.*)\s*=\s*\$\((.*)\)(.*)/; + if ($macro eq "") { # true if no macro is present + # the following looks for + # prefix = post + ($prefix,$post) = /(.*)\s*=\s*(.*)/; + } else { + $base = $applications{$macro}; + if ($base eq "") { + #print "error: $macro was not previously defined\n"; + } else { + $post = $base . $post; + } + } + $applications{$prefix} = $post; + if ( -d "$post") { #check that directory exists + print OUT "\n"; + if ( -d "$post/bin/$arch") { #check that directory exists + print OUT "${prefix}_BIN = $post/bin/${arch}\n"; + } + if ( -d "$post/lib/$arch") { #check that directory exists + print OUT "${prefix}_LIB = $post/lib/${arch}\n"; + } + if ( -d "$post/include") { #check that directory exists + print OUT "EPICS_INCLUDES += -I$post/include\n"; + } + if ( -d "$post/dbd") { #check that directory exists + print OUT "EPICS_DBDFLAGS += -I $post/dbd\n"; + } + } + } + close IN; + } +} +close OUT; diff --git a/config/makeDbDepends.pl b/config/makeDbDepends.pl new file mode 100644 index 00000000..3069fb3e --- /dev/null +++ b/config/makeDbDepends.pl @@ -0,0 +1,23 @@ +eval 'exec perl -S $0 ${1+"$@"}' # -*- Mode: perl -*- + if $running_under_some_shell; # makeDbDepends.pl + +# Called from within the object directory. +# Searches the .substitutions files (from the command line) for +# "file xxx {" entries to create a .DEPENDS file + +open(OUT, ">.DEPENDS") or die "Cannot open .DEPENDS: $!"; + +foreach $file (@ARGV) { + open(IN, "<$file") or die "Cannot open $file: $!"; + @substfile = ; + close IN or die "Cannot close $file: $!"; + + @depends = grep { s/^\s*file\s*(.*)\s*\{.*$/\1/ } @substfile; + chomp @depends; + + if (@depends) { + $file =~ s/\.substitutions/\.t.db.raw/; + print OUT "${file}:: @depends\n"; + } +} +close OUT or die "Cannot close $file: $!"; diff --git a/config/makeIocCdCommands.pl b/config/makeIocCdCommands.pl new file mode 100644 index 00000000..0c1962f0 --- /dev/null +++ b/config/makeIocCdCommands.pl @@ -0,0 +1,60 @@ +eval 'exec perl -S $0 ${1+"$@"}' # -*- Mode: perl -*- + if $running_under_some_shell; # makeIocCdCommands.pl + +use Cwd; + +$cwd = cwd(); +#hack for sun4 +$cwd =~ s|/tmp_mnt||; +$arch = $ARGV[0]; + +unlink("cdCommands"); +open(OUT,">cdCommands") or die "$! opening cdCommands"; +print OUT "startup = \"$cwd\"\n"; + +#appbin is kept for compatibility with 3.13.1 +$appbin = $cwd; +$appbin =~ s/iocBoot.*//; +$appbin = $appbin . "/bin/${arch}"; +print OUT "appbin = \"$appbin\"\n"; + +$top = $cwd; +$top =~ s/\/iocBoot.*//; +print OUT "top = \"$top\"\n"; +$topbin = "${top}/bin/${arch}"; +#skip check that top/bin/${arch} exists; src may not have been builT +print OUT "topbin = \"$topbin\"\n"; +$release = "$top/config/RELEASE"; +if (-r "$release") { + open(IN, "$release") or die "Cannot open $release\n"; + while ($line = ) { + next if ( $line =~ /\s*#/ ); + chomp($line); + $_ = $line; + #the following looks for + # prefix = $(macro)post + ($prefix,$macro,$post) = /(.*)\s*=\s*\$\((.*)\)(.*)/; + if ($macro eq "") { # true if no macro is present + # the following looks for + # prefix = post + ($prefix,$post) = /(.*)\s*=\s*(.*)/; + } else { + $base = $applications{$macro}; + if ($base eq "") { + print "error: $macro was not previously defined\n"; + } else { + $post = $base . $post; + } + } + $applications{$prefix} = $post; + $app = lc($prefix); + if ( -d "$post") { #check that directory exists + print OUT "$app = \"$post\"\n"; + } + if ( -d "$post/bin/$arch") { #check that directory exists + print OUT "${app}bin = \"$post/bin/$arch\"\n"; + } + } + close IN; +} +close OUT; diff --git a/config/replaceVAR.pl b/config/replaceVAR.pl new file mode 100644 index 00000000..e8747400 --- /dev/null +++ b/config/replaceVAR.pl @@ -0,0 +1,12 @@ +eval 'exec perl -S $0 ${1+"$@"}' # -*- Mode: perl -*- + if $running_under_some_shell; # replaceVAR.pl + +# Called from within the object directory +# Replaces VAR(xxx) with $(xxx) +# and VAR_xxx_ with $(xxx) + +while () { + s/VAR\(/\$\(/g; + s/VAR_([^_]*)_/\$\($1\)/g; + print; +} diff --git a/documentation/Makefile b/documentation/Makefile new file mode 100644 index 00000000..73751797 --- /dev/null +++ b/documentation/Makefile @@ -0,0 +1,3 @@ +TOP=.. +include $(TOP)/config/CONFIG_APP +include $(EPICS_BASE)/config/RULES_ARCHS diff --git a/documentation/Makefile.Host b/documentation/Makefile.Host new file mode 100644 index 00000000..e7e2f58b --- /dev/null +++ b/documentation/Makefile.Host @@ -0,0 +1,6 @@ +TOP = ../.. +include $(TOP)/config/CONFIG_APP + +HTMLS += motorRecord.html + +include $(TOP)/config/RULES.Host diff --git a/documentation/motorRecord.html b/documentation/motorRecord.html new file mode 100644 index 00000000..7758258f --- /dev/null +++ b/documentation/motorRecord.html @@ -0,0 +1,4565 @@ + + + + + + Motor Record and related software + + + +

+Motor Record and related software

+ +
+Tim Mooney, Joe Sullivan, Ron Sluiter
+ +
+
+

+Contents

+ + + +

+Overview

+This documentation describes version 4.1 of the EPICS motor record, and +related EPICS software required to build and use it.  Version 4.1 +of the motor record is compatible with EPICS baseR3.13.1.1 and above. +

The motor record is intended to support motors of all kinds, but currently +supports only the following variety of motor controllers (in addition to +Soft Channel support): +

    +
  • +Oregon Microsystems VME8, VME44 and VME58 series.
  • + +
  • +Highland Technologies V540.
  • + +
  • +Newport MM3000, MM4000 and MM4005.
  • +
+The record maintains two coordinate systems for motor position ("user" +and "dial" coordinates); displays drive and readback values; enforces limits +to motor motion and maintains those limits in both coordinate systems; +displays the states of limit switches; can use a home switch; optionally +takes out backlash in a user-defined direction; and provides a mechanism +by which the user and other EPICS records can recalibrate the motor position +in either coordinate system. The record also supports "tweak", "jog", and +"home" motions, and supports both absolute and relative motions in user +coordinates. Two "stop" switches are provided: a simple one for use by +other records and by channel-access clients, and a more versatile one for +interactive use. +

Except where specified otherwise, fields associated with the motor position +and its derivatives take values in user-specified "engineering units", +such as degrees; the engineering unit name is contained in the field EGU. +Thus, generally, speeds are expressed in EGU's per second. Accelerations, +however, are expressed as the number of seconds taken to accelerate to +full speed. However, additional fields are provided so that the motor position +can be specified in steps and the speed in revolutions per second, and +so that the step size can be set by specifying the number of steps per +revolution and the number of EGU's per revolution. +

The motor record can read motor position from the controller's readback +register or encoder register, or from any other EPICS record, via an EPICS +input link. While the motor is moving, the record can trigger an output +link periodically, to send readback information to other EPICS records. +When a complete motion (possibly including backlash takeout) is finished, +the record can trigger a forward link to process other EPICS records. +

The motor record can force its drive fields to agree with its readback +fields, and it does so in a variety of circumstances (e.g., when the user +tells a motor to stop, and when a limit switch is hit). Therefore, if you +are driving the motor record's VAL or DVAL field with the output of another +record, and you want that record always to contain the same value as the +motor record, you must handle this behavior in the database. One way to +do this is to forward link the motor record to a soft analog output record, +and to cause that AO record to grab the motor record's VAL or DVAL field +and poke it into your record. +

The motor record is unlike most other EPICS records in that its processing +is neither "synchronous" nor "asynchronous", as these terms are used in +the EPICS Record Reference Manual. Currently, the PACT field is always +FALSE after record processing has completed, even though a motor motion +may be in progress. This means the record always responds to channel-access +puts, and can be stopped or retargeted at any time. The record's forward +link is not executed until the motor has stopped and no motion requests +are pending.  +

+Field Descriptions

+In addition to fields common to all record types (see the EPICS +Record Reference Manual for these) the motor record has the fields +described below. + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

+Alphabetical list of record-specific fields

+NOTE: Hot links in this table take you only to the section in which +the linked item is described in detail. You'll probably have to scroll +down to find the actual item.
Name Access Prompt Data type Comment
ACCLR/W Seconds to Velocity FLOAT acceleration time
ATHMAt HOME SHORT uses the HOME switch
BACCR/W BL Seconds to Veloc. FLOAT backlash acceleration time
BDSTR/W BL Distance (EGU) FLOAT backlash distance
BVELR/W BL Velocity (EGU/s) FLOAT backlash speed
CARDCard Number SHORT EPICS card #
CBAKNoneCallback structureNOACCESS
CNENR/WEnable controlRECCHOICE(0:"Disable", 1:"Enable")
DCOFR/WDerivative GainFLOAT
DHLMR/W* Dial High Limit FLOAT 
DIFFDifference dval-drbv DOUBLE 
DINPR/WDMOV Input LinkINLINK
DIRR/W* User Direction RECCHOICE (0:"Pos", 1:"Neg")
DLLMR/W* Dial Low Limit FLOAT 
DLYR/WReadback settle time (s)FLOAT
DMOVDone moving to value SHORT The "done" flag
DOLDesired Output LocINLINK only for closed-loop mode
DRBVDial Readback Value DOUBLE 
DVALR/W* Dial Desired Value DOUBLE 
EGUR/W Engineering Units STRING 
ERESR/W* Encoder Step Size (EGU) FLOAT 
FOFR/W Freeze Offset SHORT 
FOFFR/W Offset-Freeze Switch RECCHOICE (0:"Variable", 1:"Frozen")
FRACR/W Move Fraction FLOAT 
HHSVR/W* Hihi Severity GBLCHOICE
HIGHR/W* High Alarm Limit FLOAT 
HIHIR/W* Hihi Alarm Limit FLOAT 
HLMR/W* User High Limit FLOAT 
HLSAt High Limit Switch SHORT 
HLSVR/W* HW Lim. Violation Svr GBLCHOICE 
HOMFR/W* Home Forward SHORT 
HOMRR/W* Home Reverse SHORT 
HOPRR/W High Operating Range FLOAT 
HSVR/W* High Severity GBLCHOICE 
ICOFR/WIntegral GainFLOAT
INITR/WStartup commandsSTRING
JOGFR/W* Jog motor Forward SHORT careful!
JOGRR/W* Jog motor Reverse SHORT careful!
LDVLLast Dial Des Val DOUBLE 
LLMR/W* User Low Limit FLOAT 
LLSAt Low Limit Switch SHORT 
LLSVR/W* Lolo Severity GBLCHOICE 
LOLOR/W* Lolo Alarm Limit FLOAT 
LOPRR/W Low Operating Range FLOAT 
LOWR/W* Low Alarm Limit FLOAT 
LRLVLast Rel Value DOUBLE 
LRVLLast Raw Des Val DOUBLE 
LSPGRLast SPMG RECCHOICE (See SPMG)
LSVR/W* Low Severity GBLCHOICE 
LVALLast User Des Val DOUBLE 
LVIOLimit violation SHORT 
MIPMotion In Progress SHORT 
MISSRan out of retries SHORT 
MMAPMonitor Mask ULONG 
MOVNMotor is moving SHORT Don't confuse with DMOV
MRESR/W* Motor Step Size (EGU) FLOAT 
MSTAMotor Status ULONG 
NMAPMonitor Mask ULONG 
OFFR/W User Offset (EGU) DOUBLE 
OMSLR/W Output Mode Select GBLCHOICE 
OUTR/WOutput Specification OUTLINK 
PCOFR/WProportional GainFLOAT
PERLR/W Periodic Limits RECCHOICE (0:"No", 1:"Yes")
PDIFRPrevious RDIFLONG
POSTR/WPost-move commandsSTRING
PPPost process command SHORT 
PRECR/W Display Precision SHORT 
PREMR/WPre-move commandsSTRING
RBVUser Readback Value DOUBLE 
RCNTRetry count SHORT 
RDBDR/W Retry Deadband (EGU) FLOAT 
RDBLReadback Location INLINK 
RDIFDifference rval-rrbv LONG 
REPRaw Encoder Position DOUBLE 
RESStep Size (EGU) FLOAT
RHLSRaw High Limit Switch SHORT 
RINPR/WRMP Input LinkINLINK
RLLSRaw Low Limit Switch SHORT 
RLNKReadback OutLink OUTLINK 
RLVR/W* Relative ValueDOUBLE 
RMPRaw Motor Position DOUBLE 
RRBVRaw Readback Value DOUBLE 
RRESR/W Readback Step Size (EGU)FLOAT 
RTRYR/W Max retry count SHORT 
RVALR/W* Raw Desired Value DOUBLE 
RVELRRaw Velocity LONG 
SR/W Speed (revs/sec)FLOAT 
SBAKR/W BL Speed (RPS) FLOAT 
SBASR/W Base Speed (RPS) FLOAT 
SETR/W Set/Use Switch RECCHOICE (0:"Use", 1:"Set")
SMAXR/WMax Velocity (RPS)FLOAT
SPMGR/W* Stop/Pause/Move/Go RECCHOICE (0:"Stop", 1:"Pause", 2:"Move", 3:"Go")
SREVR/W* Steps per Revolution LONG 
SSETR/W Set SET Mode SHORT 
STOOR/WSTOP OutLinkOUTLINK
STOPR/W* Stop SHORT 
SUSER/W Set USE Mode SHORT 
TDIRDirection of Travel SHORT 
TWFR/W* Tweak motor Forward SHORT 
TWRR/W* Tweak motor Reverse SHORT 
TWVR/W* Tweak Step Size (EGU) FLOAT 
UEIPR/W* Use Encoder If Present RECCHOICE (0:"No", 1:"Yes")
UREVR/W* EGU's per Revolution FLOAT 
URIPR/W* Use RDBL Link If PresentRECCHOICE (0:"No", 1:"Yes")
VALR/W* User Desired ValueDOUBLE 
VBASR/W Base Velocity (EGU/s) FLOAT 
VELOR/W Velocity (EGU/s) FLOAT 
VERSCode Version FLOAT e.g., "1.95"
VMAXR/WMax Velocity (EGU/s)FLOAT
VOFR/W Variable Offset SHORT 
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Note: In the Access column above: 
RRead only
R/WRead and write are allowed
R/W*Read and write are allowed; write triggers record processing if the +record's SCAN field is set to "Passive."
NNo access allowed
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

+Calibration-related fields

+
Name Access Prompt Data type Comments 
DIR R/W* User Direction RECCHOICE (0:"Pos", 1:"Neg") 
User and dial values are related by the equation  +
userVAL = DialVAL * DIR + OFFset +
This field is the "DIR" in the above equation. 
OFF R/W User Offset (EGU) DOUBLE User and dial coordinates can differ by a sign (the DIR field) and +an offset (OFF), according to the following equation:  +
userVAL = DialVAL * DIR + OFFset +
This field is "OFFset" in the above equation. It is not normally written +to directly by the user. 
FOFF R/W Offset-Freeze Switch RECCHOICE (0:"Variable", 1:"Frozen")
VOF R/W Variable Offset SHORT Set Offset switch (FOFF) to "Variable".
FOF R/W Freeze Offset SHORT Set Offset switch (FOFF) to "Frozen".
The user can cause the difference between user and dial +coordinates to remain fixed (i.e., the record will not change it, although +the user may) by setting FOFF to "Frozen." The fields VOF and FOF are intended +for use in backup/restore operations; any write to them will drive the +FOFF field to "Variable" (VOF) or "Frozen" (FOF). 
SET R/W Set/Use Switch RECCHOICE (0:"Use", 1:"Set")
SSET R/W Set SET Mode SHORT Set Set/Use switch to "Set".
SUSE R/W Set USE Mode SHORT Set Set/Use switch to "Use".
SET is a toggle switch used in calibrating the motor's +user and dial positions:  +

When SET = 0 ("Use"), writes to the user-coordinate drive field (VAL) +cause the dial-coordinate drive field (DVAL) to change, and the motor to +move. Writes to the dial-coordinate drive field (DVAL) cause the user-coordinate +drive field (VAL) to change, and the motor to move. +

When SET = 1 ("Set"), writes to the dial-coordinate drive field (DVAL) +and to the raw drive field (RVAL) cause a new raw motor position to be +loaded into the hardware without any change to the user-coordinate drive +field (VAL). Writes to other fields that would normally move the motor, +change the user-coordinate drive field (VAL), and the offset between user +and dial coordinates (the OFF field), with corresponding changes in the +user-coordinate limit fields (HLM and LLM). When the offset is frozen (FOFF=1), +writes to any drive field affect both user and dial values, and also load +the hardware position register.

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

+Motor-resolution fields

+
Name Access Prompt Data type Comments 
MRES R/W* Motor Step Size (EGU) FLOAT May be positive or negative
SREV R/W* Steps per Revolution LONG Must be strictly positive
UREV R/W* EGU's per Revolution FLOAT May be positive or negative 
MRES, and (SREV,UREV) represent two ways of specifying +the motor resolution--the distance or angle, in engineering units (EGU's), +associated with a single motor step. The equation relating these quantities +is "MRES = UREV/SREV". Initially, SREV has the value 200, the number of +full steps per revolution for most stepper motors, and the record never +changes this field. Only the user can change it.  +

When MRES is changed, the motor record sets UREV = MRES*SREV. When UREV +or SREV is changed, the motor sets MRES = UREV/SREV. In all cases, the +effect of a motor resolution change on the reported motor position depends +in a simple way on the value of the SET field: +
If (SET = 1), new user and dial values (VAL, DVAL) are calculated from +the existing raw value (RVAL). +
If (SET = 0), a new raw value is calculated from the existing dial +value. +
The motor doesn't move in either case; neither does the actual motor +speed (in revolutions per second) change.  +

If either MRES or UREV is changed, motor speeds that are expressed in +engineering units per second (i.e., those whose names contain the letter +'V': VELO, BVEL, VMAX and VBAS) are automatically adjusted by the motor +record according to the following equations: VELO = UREV * S; BVEL = UREV +* SBAK; VMAX = UREV * SMAX; VBAS = UREV * SBAS.  Motor speeds that +are expressed in revolutions per second (S, SBAK, and SBAS) are independent +of changes to MRES or UREV.  In contrast, when SREV is changed, only +MRES is adjusted by the motor record, thus allowing all other fields to +remain unaffected. +

Currently, changes to motor-resolution fields have no effect on the +values of limit fields (although they should).  +

MRES or UREV allow negative values so that the user/dial coordinate +systems can be configured to the opposite polarity of the motor controller's.

ERES R/W* Encoder Step Size (EGU) FLOAT Encoder resolution: the distance or angle, in engineering units, associated +with a single encoder step. ERES may be positive or negative, but currently +it must have the same sign as MRES. 
RRES R/W Readback Step Size (EGU) FLOAT Readback-device resolution: the distance or angle, in engineering units, +associated with a unit change of the number retrieved via the readback- +location input link (RDBL). RRES may be either positive or negative. 
RES Step Size (EGU) FLOAT The motor or encoder resolution, in engineering units, as selected +by the presence of an encoder supported by the hardware and by the switch +UEIP ("Use Encoder If Present"). If an encoder is present, and UEIP=1, +then RES will be set equal to ERES, otherwise RES will be set equal to +MRES. 
UEIP R/W* Use Encoder If Present RECCHOICE (0:"No", 1:"Yes") Switch: nonzero value tells the record to read the encoder (if the +hardware indicates an encoder is present) and to ignore the value read +back from the hardware's step-count register. 
URIP R/W* Use RDBL Link If Present RECCHOICE (0:"No", 1:"Yes") Switch: nonzero value tells the record to get the motor position from +the readback-location link (RDBL) (if it contains valid EPICS link information, +and if no error occurs in the attempt to read from the link) and to ignore +values read back from the hardware's step-count and encoder registers.
These switches also direct the record to calculate destinations +in relative, rather than absolute, terms, since the ratio of encoder and +readback units to motor steps may not actually be constant.
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

+Motion-related fields

+
Name Access Prompt Data type Comments 
VMAXR/WMax Velocity (EGU/s)FLOATValid range; 0 <= VMAX.  VMAX=0 disables maximum velocity range +checking.
SMAXR/WMax Velocity (RPS)FLOATValid range; 0 <= SMAX
VBASR/WBase Velocity (EGU/s) FLOATValid range; 0 <= VBAS
SBASR/WBase Speed (RPS) FLOATValid range; 0 <= SBAS
Range checking is done in such a way that any minimum +(i.e., VBAS/SBAS) or maximum (i.e., VMAX/SMAX) value entered is valid.  +For example, if the minimum is entered and it exceeds the maximum, then +the maximum is set to the new minimum value.  A VMAX value of zero +disables maximum velocity range checking. +

Slew (VELO/S) and backup (BVEL/SBAK) velocity fields are silently forced +by the motor record to be within the range set by VMAX/SMAX and VBAS/SBAS, +inclusively. +

Those who use both BURT and VMAX (i.e., nonzero VMAX) should insure +that VMAX and VBAS are placed before VELO and BVEL in their BURT request +files. +

The intent of VBAS/SBAS is to prevent the motor from moving at speeds +slow enough to excite its resonance, which can cause the motor to miss +steps. The motor is expected to accelerate from a stand-still to VBAS in +one motor pulse. 

VELO R/W Velocity (EGU/s) FLOATValid range; VBAS <= VELO <= VMAX
R/WSpeed (revs/sec) FLOAT Valid range;  SBAS <=      S      +<= SMAX
VELO is the speed, in engineering units per second, at +which the motor is move after the acceleration phase of a motion is finished. +S is the same speed expressed in revolutions per second. The record makes +sure that VELO and S are consistent, using the equation S = VELO/UREV.
ACCL R/W Seconds to Velocity FLOAT The length, in seconds, of the acceleration and deceleration phases +of a motor motion. 
The motor record expects the hardware to produce a trapezoidal +speed profile. That is, the motor speed is expected to increase linearly +with time from the base speed, VBAS, to the full speed, VELO, in ACCL seconds. +At the end of a motion, the speed is expected to decrease similarly to +VBAS. 
BDST R/W BL Distance (EGU) FLOATThe signed distance, in dial coordinates, used for backlash takeout. 
The algorithm used in moves to a (dial-coordinate) position +called "TARGET" follows:  +

1) If the motor is to move a distance greater than the magnitude of +BDST, or if the motor is to move in a direction opposite to the sign of +BDST, then the motor will move first to position (TARGET-BDST), at an acceleration +specified by ACCL and speed VELO, and then to position TARGET, at an acceleration +specified by BACC and speed BVEL.  +

2) If the motor is to move a distance smaller than the magnitude of +BDST, and if the motor is to move in the same direction as the sign of +BDST, then backlash is assumed already to have been taken out, and the +motor will move to position TARGET at an acceleration specified by BACC +and speed BVEL. 

BVEL R/WBL Velocity (EGU/s) FLOAT
SBAK R/W BL Speed (RPS) FLOAT 
BVEL is the speed, in engineering units per second, at +which the motor is move after the acceleration phase of a backlash-takeout +motion is finished. SBAK is the same speed expressed in revolutions per +second. Neither BVEL nor SBAK may be negative or zero. 
BACC R/W BL Seconds to Veloc. FLOAT The length, in seconds, of the acceleration and deceleration phases +of a backlash-takeout motion. See discussion of the acceleration field +ACCL for more specific information. 
FRACR/W Move Fraction FLOAT 
This field supports closed-loop control of pathological +devices for which drive values are not expected to compare reproducibly +with readback values. (Inchworms and other friction-driven devices are +good examples: the number of steps taken by an inchworm motor is a very +poor indicator of the distance it has traveled.)  +

In a move from position CURRENT to position TARGET, the motor record +will ask hardware to move a distance FRAC*(TARGET-CURRENT). When that motion +is complete, the record will request a motion of FRAC*(remaining distance), +and so on until the target position has been reached. 

RDBD R/W Retry Deadband (EGU) FLOAT When the motor has finished a complete motion, possibly including backlash +takeout, the motor record will compare its current position with the desired +position. If the magnitude of the difference is greater than RDBD, the +motor will try again, as if the user had requested a move from the now +current position to the desired position. Only a limited number of retries +will be performed (see RTRY). 
RTRY R/W Max retry count SHORT The maximum number of times the motor record will try again to move +to the desired position. When the retry limit is reached, the motor record +will declare the motion finished. If the desired position was not reached, +the field MISS will be set to 1.
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

+Link-related fields

+
Name Access Prompt Data type Comments 
OUT R/W Output Specification OUTLINK If Soft Channel device support is specified, this field is an EPICS +link; each time DVAL is changed, device support puts DVAL to this link.  +Otherwise, this field specifies the hardware to be controlled.
RDBL Readback Location INLINK This field specifies the field (of this or any other EPICS record) +from which the motor's current position is to be read when the field URIP +(Use Readback If Present) has the value "Yes" (1). If this field does not +contain a valid EPICS link, the URIP may as well have the value "No" (0).  +If Soft Channel device support is specified, this field is monitored for +value changes by a CA event task. 
DOL Desired Output Loc INLINK If this field contains a valid EPICS link, and the OMSL field has the +value "closed_loop" (1), then every time the the motor record is processed, +it will get a value for the VAL field from the link and move to that location, +ignoring all other drive fields. Closed-loop mode has not been tested extensively. 
OMSL R/W Output Mode Select GBLCHOICE (0:"supervisory", 1:"closed_loop")  +
If this field has the value "closed_loop" (1), and the field DOL contains +a valid EPICS link, then every time the the motor record is processed, +it will get a value for the VAL field from the link and move to that location, +ignoring all other drive fields. Closed-loop mode has not been tested extensively. 
RLNK Readback OutLink OUTLINK If this field contains a valid EPICS link, then every time the motor +record is processed, it will put the (engineering-unit) readback value +RBV to that link. 
DINPR/WDMOV Input LinkINLINKIf Soft Channel device support is specified, the value specified by +this link is used to set the DONE bit in the MSTA field; which in turn +sets the DMOV field.
RINPR/WRMP Input LinkINLINKIf Soft Channel device support is specified, the value specified by +this link is used to set the RMP field.
STOOR/WSTOP OutLinkOUTLINKIf Soft Channel device support is specified, a one is written to the  +specified link each time the STOP_AXIS motor command is issued.
+

+Soft Channel Device Driver

+The Soft Channel database links (i.e., DINP, RINP and STOO) are only processed +when the Soft Channel device driver is selected.  These links are +ignored when using any other Motor Record device driver. +
The input links (i.e., DINP, RDBL and RINP) are monitored for value +changes by a CA event task.  Users must choose either a dial input +link (RDBL) or a raw input link (RINP), but not both. At this time, the +above links are not dynamically retargetable.  +
Note that JOG[F/R] does not work with the Soft Channel device driver.
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

+Limit-related fields

+
Name AccessPrompt Data type Comments
HLM R/W* User High LimitFLOAT The maximum allowed value of the VAL field. If HLM changes so that +VAL is no longer less than HLM, then the record will set the field LVIO +to 1. If the DIR field has the value "Pos", then HLM will always be consistent +with DHLM, otherwise HLM will always be consistent with DLLM.
LLM R/W* User Low Limit FLOAT The minimum allowed value of the VAL field. If LLM changes so that +VAL is no longer greater than LLM, then the record will set the field LVIO +to 1. If the DIR field has the value "Pos", then LLM will always be consistent +with DLLM, otherwise LLM will always be consistent with DHLM. 
DHLM R/W* Dial High Limit FLOAT The maximum allowed value of the DVAL field. If DHLM changes so that +DVAL is no longer less than DHLM, then the record will set the field LVIO +to 1.. If the DIR field has the value "Pos", then DHLM will always be consistent +with HLM, otherwise DHLM will always be consistent with LLM. 
DLLM R/W* Dial Low Limit FLOAT The minimum allowed value of the DVAL field. If DLLM changes so that +DVAL is no longer greater than DLLM, then the record will set the field +LVIO to 1. If the DIR field has the value "Pos", then DLLM will always +be consistent with LLM, otherwise DLLM will always be consistent with HLM. 
LVIO Limit violation SHORT A value of 1 indicates that the dial-value drive field, DVAL, or the +dial-value readback field, DRBV, is outside of the limits (DHLM, DLLM), +and this prevents the motor from moving. If the backlash distance, BDST, +is non-zero, it further restricts the allowable range of DVAL. When a JOG +button is hit, LVIO goes to 1 and stops the motor if/when DVAL gets to +within one second's travel time of either limit. 
PERL R/W Periodic Limits RECCHOICE (0:"No", 1:"Yes")  +
Not implemented. Originally intended to support periodic "limits" on +the VAL field (such as those associated with a rotation stage--e.g., [0...360] +or [-180...180]) independently of the actual soft limits HLM and LLM. 
HOPR R/W High Operating Range FLOAT Not used. See HLM and DHLM. 
LOPR R/W Low Operating Range FLOAT Not used. See LLM and DLLM. 
HLS At High Limit Switch SHORT
RHLS Raw High Limit SwitchSHORT 
If either of these fields is nonzero, then the motor is +at the positive-limit switch, where the positive sense is that of the user-coordinate +system for HLS, and that of the raw (step-number) coordinate system for +RHLS. 
LLS RAt Low Limit Switch SHORT
RLLS Raw Low Limit SwitchSHORT 
If either of these fields is nonzero, then the motor is +at the negative-limit switch, where the positive sense is that of the user-coordinate +system for LLS, and that of the raw (step-number) coordinate system for +RLLS.
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

+Command-button fields

+
Name Access PromptData type Comments 
SPMG R/W* Stop/Pause/Move/Go RECCHOICE (0:"Stop", 1:"Pause", 2:"Move", 3:"Go") 
This field is intended primarily for interactive use, and +normally has the value "Go."  +

If the user sets this field to "Stop," the motor will decelerate to +a stop, the VAL field will be set equal to the RBV field, and the DVAL +field will be set equal to the DRBV field. (These actions ensure that the +motor will not start moving again until a drive field is changed.) In any +case, the motor will not move while SPMG has the value "Stop" or "Pause."  +

If "SPMG" has the value "Move," the motor record will reset SPMG to +"Pause" when a motion completes. This behavior supports users who want +a motor to sit still until they say "Move", no matter what changes occur +in the drive fields. 

STOP R/W* Stop SHORT 
When this field is set to 1, the record will immediately +reset it to 0, and the motor will decelerate to a stop. When the motor +has stopped, VAL will be set equal to RBV, and DVAL will be set equal to +DRBV. (This ensures that the motor will not start moving the next time +the record is processed, unless a drive field is explicitly changed. If +you want the motor to pause, use the SPMG field.) 
HOMF R/W* Home Forward SHORT
HOMR R/W* Home Reverse SHORT 
When one of these fields is set to 1, the motor will decelerate +to a stop if already moving, move in the indicated direction (in dial +coordinates) at the acceleration specified by ACCL and a speed of 1000 +motor pulses per second, until the hardware detects the "home" switch has +become active. Then the hardware will do something hardware dependent in +response to its "home" command, if any. (The OMS hardware causes the motor +to decelerate to a stop.) When the motor stops, the VAL field will be set +equal to the RBV field, and the DVAL field will be set equal to the DRBV +field.  These fields can be set to 1, but setting either field to +0 results in an error.  The record sets HOM[F/R] to zero when the +homing procedure is either completed or aborted.
JOGF R/W* Jog motor Forward SHORT
JOGR R/W* Jog motor Reverse SHORT 
When one of these fields is set to 1, the motor will decelerate +to a stop if already moving, and move in the indicated direction (in user +coordinates) at an acceleration specified by ACCL and speed VELO, until +the field goes to 0. Then the motor will set VAL to RBV and DVAL to DRBV, +decelerate to a stop, and execute a (backlash-corrected, if BDST is nonzero) +move to the position at which the field went to 0.  +

These fields are dangerous when used over channel access, because the +motor does not stop moving until a second message is received. If a very +busy network should cause that second message to be lost, the motor will +travel to its limit switch or hard stop. 

TWF R/W* Tweak motor Forward SHORT
TWR R/W* Tweak motor Reverse SHORT 
When one of these fields is set to 1, the record will immediately +reset it to 0, and the motor will move (with backlash takeout if BDST is +nonzero) by a distance TWV (in user coordinates) at the acceleration specified +by ACCL and at speed VELO.
TWV R/W* Tweak Step Size (EGU) FLOAT This field contains the distance the motor is to move in response to +the TWF and TWR buttons. 
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

+Drive fields

+
Name Access Prompt Data type Comments
VAL R/W*User Desired Value DOUBLEThis is the desired position in user coordinates. When this field is +written to, DVAL and RVAL will be changed correspondingly, and the motor +will move (with backlash takeout if BDST is nonzero) to the newly written +position. 
DVAL R/W* Dial Desired Value DOUBLE This is the desired position in dial coordinates. When this field is +written to, VAL and RVAL will be changed correspondingly, and the motor +will move (with backlash takeout if BDST is nonzero) to the newly written +position. 
RVAL R/W* Raw Desired Value DOUBLE This is the desired position in raw coordinates. When this field is +written to, VAL and DVAL will be changed correspondingly, and the motor +will move (with backlash takeout if BDST is nonzero) to the newly written +position. 
RLV R/W* Relative Value DOUBLE When this field is changed, its value will be added to VAL, the field +itself will immediately be reset to 0, and the motor record will behave +as though the VAL field had been changed directly. 
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

+Status/readback fields

+
Name Access Prompt Data type Comments 
RBV User Readback Value DOUBLEThe current motor position, in user coordinates, from the motor hardware +(default), or from the encoder supported by the motor-controller hardware +(if UEIP is nonzero), or from the readback link RDBL (if URIP is nonzero). 
DRBV Dial Readback Value DOUBLE The current motor position, in dial coordinates, from the motor hardware +(default), or from the encoder supported by the motor-controller hardware +(if UEIP is nonzero), or from the readback link RDBL (if URIP is nonzero). 
DMOV Done moving to value SHORT 
This field is set to 0 when the motor record begins a motion, +and remains 0 through any retries and backlash corrections that may be +required until the motor record has completely finished that motion, whereupon +the field is set to 1. DMOV is guaranteed to execute and post a 1/0/1 pulse +when the motor is commanded to move--even if no motion actually occurs +because the motor was commanded to move to its current position. 
MOVN Motor is moving SHORT This field is set to 1 while the record believes that the motor actually +is moving. This field is not the inverse of DMOV, since it may go to zero +during a complex motion, if that motion includes a momentary stop. 
DLYR/WReadback settle time (s)FLOATDelay (in seconds) the time between motor controller done and motor +record done (i.e., DMOV).
DIFF Difference dval-drbv DOUBLE DIFF is the difference, in engineering units, between the +desired motor position, and the readback device's report of the current +position. RDIF is the same difference in "raw" units (normally, steps). 
RDIF Difference rval-rrbv LONG 
RRBV Raw Readback Value DOUBLE The current position of the motor, encoder, or readback link, as received +from whatever source has been selected to provide position information. +The units associated with this field depend on the source. 
RMP Raw Motor Position DOUBLE The contents of the hardware's step-count register. This field contains +the same information as the dial value, but in steps, rather than in engineering +units. 
REP Raw Encoder Position DOUBLE The contents of the hardware's encoder-count register. Ideally, this +field contains the same information as the dial value, but in encoder counts, +rather than in engineering units. 
MSTA Motor Status ULONG The motor status as received from the hardware.  The MSTA bits +are defined as follows: +
+
    +
  1. +DIRECTION: (0:Negative, 1:Positive)
  2. + +
  3. +DONE: motion is complete.
  4. + +
  5. +OVERTRAVEL: a limit switch has been hit.
  6. + +
  7. +HOMELS: state of the home limit switch.
  8. + +
  9. +Unused
  10. + +
  11. +POSITION: closed-loop position control is enabled.
  12. + +
  13. +Unused
  14. + +
  15. +HOME: if at home position.
  16. + +
  17. +PRESENT: encoder is present.
  18. + +
  19. +PROBLEM: driver stopped polling.
  20. + +
  21. +MOVING: non-zero velocity present.
  22. + +
  23. +GAIN_SUPPORT: motor supports closed-loop position control.
  24. +
+
+
TDIR Direction of Travel SHORT The direction in which the motor is currently traveling (or was most +recently traveling), as received from the hardware. If 0, the raw readback +value should be decreasing. 
ATHM At HOME SHORT The state of the hardware's "home" switch. If 1, the motor has hit +the switch. 
RCNT Retry count SHORT The number of times the motor record has detected failure of the motor +to land within the retry-deadband distance of the desired position. 
MISS Ran out of retries SHORT If 1, the motor has failed to land on the desired position more than +the allowed number of times. This field will be reset the next time the +motor succeeds in reaching the desired position. 
RVEL Raw Velocity LONG Speed in steps per second that the motor actually is moving. 
+ +
+
+ +
+

+Servo fields

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 
PID related record fields accept only normalized +values (i.e., 0.0 <= value <= 1.0).  Before sending them to +the motor controller, device support scales the record fields to valid +motor controller parameters,.  Let the motor controller PID parameters +be represented by CKP, CKI and CKD; then the PID coefficients are converted +from motor record fields to controller parameters as follows: +

For the MM4000;  CKP = PCOF, CKI = ICOF and CKD = DCOF. +
For all OMS controllers;  CKP = 1999.9 * PCOF, CKI = 1999.9 * +ICOF, CKD = 1999.9 * DCOF. +

Note the following: +

    +
  • +When commanded to move the OMS control law is a PD loop, when it is holding +a position it is a PID loop.
  • + +
  • +The Proportional Gain cannot be turned off (i.e., set to zero) in an OMS +controller.  The minimum value is PCOF = 0.00005 (CKP = 0.1).  +If the user sets PCOF < 0.00005 for an OMS controller, device support +silently resets it to it's minimum value of 0.00005.
  • +
+
+
Name
+
+
Access
+
+
Prompt
+
+
Data type
+
+
Comments
+
CNENR/WEnable controlRECCHOICE (0:"Disable", 1:"Enable")  Enable/Disable closed-loop position +control.  This field is active only if the GAIN_SUPPORT bit in the +MSTA is true.  This field is set by both the user and device support.  +CNEN is set to Disable by device support when it detects a motion +controller error; e.g. maximum following error exceeded.
PCOFR/WProportional GainFLOATValid range; 0.0 <= PCOF <= 1.0
 ICOFR/WIntegral GainFLOATValid range; 0.0 <= ICOF <= 1.0
 DCOFR/WDerivative GainFLOATValid range; 0.0 <= DCOF <= 1.0
+ +
  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+

+Alarm fields

+
Name Access Prompt Data typeComments 
HIHI R/W* Hihi Alarm Limit FLOAT
LOLO R/W* Lolo Alarm Limit FLOAT
HIGH R/W* High Alarm Limit FLOAT
LOW R/W* Low Alarm Limit FLOAT
HHSV R/W* Hihi Severity GBLCHOICENot used.
LLSV R/W* Lolo Severity GBLCHOICENot used.
HSV R/W* High Severity GBLCHOICENot used.
LSV R/W* Low Severity GBLCHOICENot used.
HLSV R/W* HW Limit Switch Violation SeverityGBLCHOICE 
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

+Miscellaneous fields

+
Name Access Prompt Data type Comments 
PREC R/WDisplay Precision SHORT The number of digits to the right of the decimal that are to be displayed +by MEDM and other channel-access clients. 
EGU R/W Engineering Units STRINGString sent to channel-access clients who ask for engineering units. 
VERS Code Version FLOAT Version number of the recMotor.c code. 
CARD Card Number SHORT For VME based devices (i.e., OMS VME8/44, OMS VME58 and V544) this +is the VME card number, derived from the output link. Cards are numbered +from zero according to their VME addresses.  Oregon Microsystems series +VME8 and VME44 cards occur in the same series, since they are handled by +the same driver.  Oregon Microsystems VME58 cards are numbered separately, +as are Highland Technology V540 cards.  This field is set to -1 for +non-VME based device/drivers.
+

+Command Primitives

+The following three fields comprise the Command Primitives feature.  +The command primitive record fields are available to the user to send ASCII +command primitives to the motor control board at fixed, predefined, times.  +Each of the fields is defined as a character string.  Consult the +motor controller manual for command protocols.  No error checking +is done by the motor record or the device driver to insure that the command +strings are valid.  Each field is terminated by the device +driver according to the command protocol.  Command primitives that +result in a response from the motion control board are valid, but the response +is not processed.  This feature is currently only available with OMS +VME8/44/58 or Newport MM4000 device support. +

Device Directives +

    +
  • +Valid only in the INIT field.
  • + +
  • +Must be identified by the following;
  • + +
    +
  • +First character of INIT string must be a '@'.
  • + +
  • +One or more characters followed by a terminating '@'; i.e., device directives +must have nonzero length.
  • + +
  • +A valid device directive; currently, only "DPM_ON".
  • +
    + +
  • +INIT strings are stripped of valid device directives (including @'s) and +tested for nonzero length before being sent to the controller.  For +example, given the INIT string, "@DPM_ON@HE", the device directive @DPM_ON@ +is stripped out before HE is sent to the controller.
  • +
+Driver Power Monitoring +
    +
  • +This feature is only available with the OMS VME58 device support.
  • + +
  • +The 8 User I/O signals are assigned to the 8 possible VME58 axes as follows:
  • + +
      User I/O #0 <> X axis +
       "   "    1 <> Y  +" +
      ........................................ +
      "    "    7 <> S  +"
    + +
  • +Drive-power monitoring defaults to disabled at boot-up.  Request enabling +drive-power monitoring by entering the device directive "@DPM_ON@" command +into the motor record initialization field (i.e., INIT).  The INIT +field is processed at record initialization (i.e., bootup), hence if there +are no errors, drive-power monitoring will be enabled after the next bootup.
  • + +
  • +Whenever a request is made to enable drive-power status monitoring, an +error check is made (using the VME58 "RB" command) to verify that the User +I/O has been configured as an input.  The following message will appear +in the error log if a configuration error is detected; "Invalid VME58 configuration; +RB = 0x####", where "####" is the VME58's response to the RB command.
  • + +
  • +When drive-power status monitoring is enabled and a power failure is detected, +the device driver will respond by activating the the RA_OVERTRAVEL bit +in the MSTA.  This results in either HLS or LLS being activated depending +on the DIR field. In addition, the following message will appear in the +error log; "Drive power failure at VME58 card#?? motor#??".
  • +
+
INITR/WStartup commandsSTRINGSent at record initialization.
PREMR/WPre-move commandsSTRINGSent before every command string that causes motion.
POSTR/WPost-move commandsSTRINGSent after a complete motion is finished or when an overtravel limit +switch is detected.
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

+Private fields

+
Name Access Prompt Data type Comments 
CBAKNoneCallback structureNOACCESS 
LVAL Last User Des Val DOUBLE
LDVL Last Dial Des Val DOUBLE
LRVL Last Raw Des Val DOUBLE
LRLV Last Rel Value DOUBLE
PDIFRPrevious RDIFLONG
PP Post process command SHORT
MIP Motion In Progress SHORT
MMAP Monitor Mask ULONG
NMAP Monitor Mask ULONG
LSPG Last SPMG RECCHOICE (see SPMG)
+ +
+

+

+Files, device support

+The following table briefly describes all of the files required to implement +and use the motor record. The reader is assumed to be familiar with the +EPICS +Application Source/Release Control document which describes how to +build an EPICS application tree into which these files are to be placed, +and how to run "makesdr" and "gnumake" to build the record support. These +files can all be obtained from the EPICS +Software Distribution (in the custom-software +section). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SOURCE CODE +
files to be placed in &lttop>/&ltapp&gtApp/src/
motorRecord.dbdDatabase Definition file for motor record.
motorRecord.c Record support for the motor record
motor.h Header included by all .c files
motordevCom.cDevice support common to all motor Record device drivers.
motordevCom.hDevice support header file.
motordrvCom.cDriver support common to all motor Record device drivers.
motordrvCom.hDriver support header file.
motordrvComCode.hLocal variables common to all motor Record drivers.
NOTE: All of the above files are required for any and all motor Record +device drivers.
devOmsCom.cDevice support common to all Oregon Microsystems device drivers.
devOmsCom.h Device support header file common to all Oregon Microsystems device +drivers.
drvOmsCom.hDriver support header file common to all Oregon Microsystems device +drivers.
NOTE: The above files are required for any and all Oregon Microsystems +device drivers.
devOms.c Device support for Oregon Microsystems VME8 and VME44 series boards
drvOms.cDriver for Oregon Microsystems VME8 and VME44 series boards
drvOms.h Header included by devOms.c and drvOms.c
devOms58.c Device support for Oregon Microsystems VME58 series boards
drvOms58.c Driver for Oregon Microsystems VME58 series boards
drvOms58.h Header included by devOms58.c and drvOms58.c
devV544.c Device support for Highland Technology boards.
drvV544.cDriver for Highland Technology boards.
drvV544.h Header included by devV544.c and drvV544.c
drvMMCom.hCommon header included by all Newport Motion Master device drivers.
gpibIO.hGPIB communication include file.
gpibIO.cGPIB interface via Hideos.
serialIO.h Serial communication include file.
serialIO.ccSerial communication interface via Hideos.
NOTE: The above files are required for any and all Newport Motion Master +device drivers.
devMM3000.cDevice support for Newport MM3000.
drvMM3000.cDriver for Newport MM3000.
devMM4000.cDevice support for Newport MM4000.
drvMM4000.cDriver for Newport MM4000.
devSoft.cSoft Channel device support.
devSoftAux.cSoft Channel device support (Note: CA and record access code cannot +both reside in the same file; each defines (redefines) the DBR's.  +Hence, functions are split between this and the above file base on whether +they are record oriented (devSoft.c) or CA oriented (devSoftAux.c).
devSoft.hHeader included by devSoft.c and devSoftAux.c
Makefile.VxThis file is not included in the distribution. However, the user must +edit this file and add lines similar to the following: +
MOTOR_OBJS       = motorRecord.o devOms.o drvOms.o devOms58.o drvOms58.o
+LIBNAME = mylib.o
+LIBOBJS = $(MOTOR_OBJS) $(other objects here)
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ASCII FILES +
files to be placed in &lttop>/cat_ascii/
motorRecord.asciiThis file defines all of the fields for the motor record.
choiceRecMotor.asciiThis file defines all of the choice values for the motor record.
choiceMotor.hThis file is included by choiceRecMotor.ascii and by recMotor.c.
choiceRec.asciiThis file is not included in the distribution. However, the user must +edit this file and add the line: +
#include &ltchoiceRecMotor.ascii>
+
dbRecType.asciiThis file is not included in the distribution. However, the user must +edit this file and add the line: +
"motor"
+
devSup.asciiThis file is not included in the distribution. However, the user must +edit this file and add the following lines: +
! Device Support for motor record
+"motor"    VME_IO    "devOMS"     "OMS VME8, VME44" 
+"motor"    VME_IO    "devOms58"   "OMS VME58" 
+"motor"    VME_IO    "devV544"    "Highland V544" 
+
drvSup.asciiThis file is not included in the distribution. However, the user must +edit or create this file and add the following lines: +
! Driver support for the OME VME58 motor controller
+"drvOms58"
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MEDM DISPLAY SCREENS +
files to be placed in &lttop>/&ltapp&gtApp/op/adl/
motorx.adlSmall motor-control screen
motorx_tiny.adl Tiny motor-control screen
motorx_more.adl Medium motor-control screen
motorx_setup.adlSetup screen for a single motor
motorx_all.adl Debug screen for a single motor
These files build medm screens to access the motor +record. To use one of them from the command line, type, for example +
medm -x -macro "P=XXX:,M=m1" motorx.adl
+where XXX:m1 is the name of a motor record in an IOC. +

These files can also be used as related displays from other medm +screens by passing the argument "P=XXX:,M=m1".

+ + + + + + + + + + + + + + + +
+
EPICS STARTUP FILES +
files to be placed in &lttop>/ioc/ioc&ltname>/
st.cmdmv167 Startup script
A sample startup script, containing excerpts relevant to +motors, is included in the distribution. Here is an annotated copy: +
#######################################################################
+# vxWorks startup script to load and execute system (iocCore) software.
+Load standard EPICS software +
# the following should be loaded first - BEGIN
+ld < targetmv167/iocCore
+ld < targetmv167/drvSup
+ld < targetmv167/devSup
+ld < targetmv167/recSup
+# the following should be loaded first - END
+Load custom EPICS software (including motor support) +
ld < ../../stdApp/src/O.mv167/stdlib.o
+Motor-related debug switches  +
recMotordebug = 0
+
+#OMS vme8/vme44 debug switches
+devOMSdebug = 0
+drvOMSdebug = 0
+
+#OMS vme58 debug switches
+devOms58debug = 0
+drvOms58debug = 0
+
+#Highland Technology V544 debug switches
+devV544ebug = 0
+drvV544debug = 0
+Motor-related databases  +
# load this before loading any databases
+dbLoad "../../default.dctsdr"
+
+#allstop
+dbLoadRecords("../../stdApp/gDb/allstop.db","P=tmm:")
+#motors
+dbLoadRecords("../../stdApp/gDb/m16.db","P=tmm:")
+Specify motor-controller board address, interrupt +vector, etc. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
   omsSetup(nCards, Unused, baseAddress, +intVectBase, intLevel, pollRate) +
 oms58Setup(nCards, Unused, baseAddress, intVectBase, intLevel, +pollRate) +
  v544Setup(nCards, nAxes,  baseAddress, intVectBase, +intLevel, pollRate) +

MM4000Setup(nCards, Unused,                                     +pollRate) +
MM4000Config(card#, portType, GPIB link or hideos_card#, GPIB address +or hideos_task name)

nCardsthe actual number of cards may be less, but not greater than this
nAxesthe maximum number of motor axes controlled by any one card
baseAddressthe base address of the first card of a series. This must agree with +address jumpers on the actual card(s). +

OMS VME8, VME44, and VMEX cards are all of one series, with a base address +in the short address space, on a 16-byte (0x10) boundary. (I.e., these +cards require 16 bytes each, and must all be addressed contiguously as, +e.g., 0xFC00, 0xFC10). +

OMS VME58-x cards are in their own series, also in the short address +space, on a 4k-byte (0x1000) boundary. +

Highland Technology V544 cards are in their own series, in the short +address space, on a 128-byte (0x80) boundary.

intVectBasethe interrupt vector that will be loaded into the first card of a series. +Succeeding cards will be loaded with intVectBase+1, intVectBase+2, etc. +Set to "0" to disable interrupt generation; otherwise, stay in the range +[64..255].
intLevelthe VME-interrupt level (in [1..6]) the cards will use. This must agree +with jumper settings on the cards.
pollRatethe rate (in Hz.) at which the driver will interrogate a card when +one of its motors is moving. This is also the rate at which channel-access +monitors will be posted; to avoid saturating the network with motor-readback +information, don't set pollRate much higher than 10 Hz. pollRate must be +in the range [1..60].
portType0 - GPIB_PORT or 1 - RS232_PORT
+ +
# OMS VME8, VME44, VMEX driver setup parameters:
+omsSetup(5, 8, 0xFC00, 180, 5, 10)
+
+# OMS VME58 driver setup parameters:
+oms58Setup(5, 8, 0x2000, 190, 5, 10)
+
+# Highland Technology V544 driver setup parameters:
+v544Setup(5, 4, 0xDD00, 200, 5, 10)
+Start EPICS +
iocInit
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
BACKUP/RESTORE (BURT) REQUEST FILES +
files to be placed in &lttop>/&ltapp&gtApp/op/burt/
settings.reqsample request file to save settings of all motors. Edit this file, +supplying names of the motor records whose settings you want saved. (The +sample file also saves the states of other records in the sample database, +m16.db, that enable or disable the motor records.)
yyMotorSettings.req save settings of a specified motor. This file is #include'd +(once for each motor) by settings.req.
positions.req sample request file to save positions of all motors. Edit this file, +supplying names of the motor records whose positions you want saved.
yyMotorPositions.req save position of a specified motor, This file is #include'd +(once for each motor) by positions.req.
These files tell the backup/restore tool how to save motor +settings and positions. To use them from the command line, type, for example +
burtrb -f settings.req -o myMotorSettings.snap
+
+burtrb -f positions.req -o myMotorPositions.snap
+
+
+To restore the motor settings and positions saved by the above commands, +type +
burtwb -f myMotorSettings.snap
+
+burtwb -f myMotorPositions.snap
+
+
+
+ +
+

+Restrictions

+You must not change the motor resolution while the motor is moving, +but the record currently does not defend itself against this. +

There is no way to ask for a reading of the motor's position register, +encoder, or limit switches. These are read only while the motor is moving. +

Because of the way limit-switch information is conveyed by the OMS hardware, +the motor record cannot know the states of both limit switches at the same +time. It only knows the state of the switch toward which the motor is moving. +

If a move is requested while the motor already is in motion, the original +move will still receive a (useless) backlash correction. +

Changing MRES (the motor resolution) should change VELO (the speed in +engineering units per second), and leave S (the speed in revolutions per +second) unchanged. Currently, neither VELO nor S appears to change, but +the record behaves as though S had been changed to agree with VELO. A similar +thing happens with SBAK and SBAS. +
+


+
+

+Examples

+ +

+Command Primitives

+This example assumes the controller is an MM4000 and that only one motor +in the controller is moved at a time (MM4000 power on/off effects all motors).  +If the user wishes the system to perform as follows: +
    +
  1. +Boot-up with motor power off.
  2. + +
  3. +Turn motor power on before motor motion.
  4. + +
  5. +Turn motor power off after motor motion.
  6. +
+Then set fields as follows: +
    +
  • +INIT = "MF"
  • + +
  • +PREM = "MO"
  • + +
  • +POST = "MF"
  • +
+ +

+Soft Channel

+The following is a simple example of using soft channel device support +to allow the user to transform rotary position commands into linear moves.  +This example assumes the system consist of a linear stage driving a tangent +arm.  The user commands the system in terms of the angle the arm makes +with the vertical.  The database converts this angle into a linear +position along an axis that is one meter perpendicular to the center of +rotation. +

The following database implements the system described above.  +The database consist of four records: +

    +
  • +rotary - a soft motor record where the user issues angular +position commands in degrees.
  • + +
  • +convertDriveValue - a calcout record that converts the commanded +position from degrees to mm.
  • + +
  • +linear -  a hard motor record using Oms58 device support +that accepts linear position commands in mm.
  • + +
  • +convertReadbackValue - calcout record that converts the feedback +position from mm +to +degrees.
  • +
+Note the following concerning the example database and Soft Channel device +support: +
    +
  • +Jogging a soft channel motor record is not supported.
  • + +
  • +In the convertReadbackValue record, CP is required in the INPA field +to get continuous readback updates to the rotary record.  Otherwise, +the rotary record will only update at the end of a move.
  • + +
  • +The convertDriveValue record converts rotary's DVAL field +from degrees to radians, takes the tangent, converts from meters to millimeters +and finally, sends this value to linear's DVAL field.
  • + +
  • +The convertReadbackValue record converts linear's DRBV field +from millimeters to meters, takes the inverse tangent and finally, converts +from radians to degrees.
  • +
+ + + + + + + + + + + + + +
grecord(motor,"$(user):rotary") +
{ +
    field(DTYP,"Soft Channel") +
    field(OUT,"$(user):convertDriveValue.A  PP +MS") +
    field(RDBL,"$(user):convertReadbackValue.VAL  +NPP MS") +
    field(URIP,"Yes") +
    field(STOO,"$(user):linear.STOP  PP MS") +
    field(DINP,"$(user):linear.DMOV  NPP MS") +
    field(MRES,"0.001") +
    field(RRES,"1.000") +
    field(PREC,"3") +
    field(DHLM,"45") +
    field(DLLM,"-45") +
    field(TWV,"5") +
    field(RTRY,"0") +
    field(EGU,"deg.") +
}
grecord(motor,"$(user):linear") +
{ +
    field(DTYP,"OMS VME58") +
    field(VBAS,"1.0") +
    field(VELO,"25.0") +
    field(OUT,"#C0 S0 @") +
    field(MRES,"0.001") +
    field(PREC,"3") +
    field(DHLM,"1000") +
    field(DLLM,"-1000") +
    field(RTRY,"0") +
    field(TWV,"1") +
    field(EGU,"mm.") +
}
grecord(calcout,"$(user):convertDriveValue") +
{ +
    field(DESC,"Convert rotary to linear") +
    field(CALC,"TAN(A / 57.296) * 1000") +
    field(OUT,"$(user):linear.DVAL  PP MS") +
}
grecord(calcout,"$(user):convertReadbackValue") +
{ +
    field(DESC,"Convert linear to rotary") +
    field(CALC,"ATAN(A / 1000) * 57.296") +
    field(INPA,"$(user):linear.DRBV  CP MS") +
}
+ +

+

+Design Decisions

+This section of the document is an attempt to record the reasoning behind +recent motor record design decisions. +

+Motor Controller Travel Limits

+For those motor controllers that provide their own internal software travel +limits (e.g., MM4000), the motor record will only allow the user to set +dial travel limits (i.e., D[H/L]LM) that are within the controller's travel +limit range, inclusively.  In making this design decision it was assumed +that an expert user would enter the controller travel limits (possibly, +through a front panel user interface) that would protect other users from +reaching the hard travel limits.  Controllers with their own travel +limits that are commanded to move outside their valid range respond with +an error.  Hence, there is no utility in allowing the dial travel +limits outside the controller's range. +

+Separate EGU's and MRES's between Controller and Motor Record

+Device driver support for controllers that have characteristics different +from the OMS controllers, such as the Newport MM4000, has raised new issues.  +One of these issues is whether or not the motor record should set it's +EGU and MRES fields from the controller's values.  We think it should +not. +

Background: The MM4000 is a stand-alone controller with it's own front +panel, non-volatile memory and power supply.  The user can configure +the MM4000 entirely from the front panel; including which engineering units +will be displayed on the front panel.  Once engineering units are +selected, these same units will be used by the MM4000 when communicating +with a host system (i.e., EPICS).  Since a host can query the MM4000 +for both the controller's engineering units and it's positioning resolution, +it is possible to have the motor record's EGU and MRES fields automatically +set based on values stored in the MM4000 controller, but we decided not +to do this. +

To maximize flexibility, we decided to keep these characteristics separate +between the motor record and the controller.  This decision allows +the motor record EGU field (and consequently, MRES) to be set to an application +specific value (e.g., kV) that is not supported by the motor controller. +
  +
  +

+


+
+Suggestions and comments to:
+ +
Ron Sluiter : (sluiter@aps.anl.gov) +
Tim Mooney : (mooney@aps.anl.gov) +
Joe Sullivan : (sullivan@aps.anl.gov) +
Last modified: July 12, 1999 + + diff --git a/documentation/motor_files.html b/documentation/motor_files.html new file mode 100644 index 00000000..444737b6 --- /dev/null +++ b/documentation/motor_files.html @@ -0,0 +1,61 @@ + + + + + + + + +
+

+Motor record source code file organization

+ +

+Record source files

+ +
motorRecord.dbd +
motor.h +
motorRecord.c
+ +

+Device driver source files

+ +
Common to all motor Record device drivers +
motordevCom.h +
motordevCom.c +
motordrvCom.h +
motordrvComCode.h +
motordrvCom.c
+ +
    Oregon Micro Systems +

    Common to all OMS device drivers +
    devOmsCom.h +
    devOmsCom.c +
    drvOmsCom.h +

    VME8/44         VME58 +
    devOms.c        devOms58.c +
    drvOms.h        drvOms58.h +
    drvOms.c        drvOms58.c +

    Newport Motion Master +

    Common to all Motion Master device drivers +
    drvMMCom.h +
    gpibIO.h +
    gpibIO.c +
    serialIO.h +
    serialIO.cc +

    MM3000                + +MM4000 +
    devMM3000.c        devMM4000.c +
    drvMM3000.c        drvMM4000.c +

    Highland V544 +
    devV544.c +
    drvV544.h +
    drvV544.c +

    Soft Channel +
    devSoft.h +
    devSoft.c +
    devSoftAux.c

+ + + diff --git a/documentation/motor_release.html b/documentation/motor_release.html new file mode 100644 index 00000000..67f688dc --- /dev/null +++ b/documentation/motor_release.html @@ -0,0 +1,531 @@ + + + + + + + + +
+

+Motor Record Version 4.1 Release Notice

+Although the motor record software in this release is compatible with EPICS +baseR3.13.1.1 and above, the directory structure, the "make" files and +the configuration files are intended for the "unbundled" architecture of +EPICS R3.13.2 and above. +
+

+Modifications to Existing Features

+Three errors in the OMS VME58 driver were found.  All the errors caused +the same problem.  Namely, erroneous retries occurred intermittently +when multiple axes were commanded to move on the same controller.  +This error occurred because old position data was being passed back from +the driver after Done was detected.  The erroneous intermittent retries +occurred more often when the Oms setup parameters called for a high frequency +(e.g., 60 Hz) "polling" rate and Oms interrupts were enabled. +
  +
+

+Motor Record Version 4.0 Release Notice

+Release 4.0 of the motor record is only compatible with EPICS baseR3.13.1.1 +and above.  This document describes changes made to the motor record +and its' associated device driver support between versions 3.5 and 4.0. +
+

+Modifications to Existing Features

+ +

+GAIN Field Removed

+Since the GAIN field is redundant (i.e., PID parameters can be set individually) +it has been removed. +
  +

+HOM[F/R] Bug Fix

+Previous releases of the motor record had a potential problem associated +with the homing function.  The motorx_all.adl V1.9 MEDM display sets +the HOM[F/R] fields on and off, corresponding to the user pressing and +releasing the respective home button.  Depending on the pollRate +defined in the st.cmd Setup command and the speed at which the user +toggled the HOM[F/R] buttons; the record level software would turn off +the DMOV field when the HOM[F/R] button was turned off and a motor status +update had not yet been received.  As a result, when the motor completed +it's homing function the command positions (i.e., VAL, DVAL, RVAL) were +not updated. +

All previous motor record releases contained the DMOV problem; the commanded +position update problem was limited to the previous release (V3.5).  +With this release, a dbPutField to either HOMF or HOMR is valid on a FALSE +to TRUE transition (i.e., 0 to 1), but a TRUE to FALSE transition (i.e., +1 to 0) will result in an error.   motorx_all.adl_V2.2 +(which is included with this distribution) sets the HOM[F/R] fields on +when the user presses the homing button, but is does not set it +off when the button is released.  The motor record clears the HOM[F/R] +field when the homing operation is done (i.e., completed or terminated). +
  +

+Initial Position

+If both the auto restore and controller target position values are non-zero +at boot-up, then DVAL will be initialized from the controller's value.  +In other words, the controller's target position takes precedence over +the autorestore value when both systems have non-zero DVAL values.  +As before, it is assumed that a zero target position from autorestore or +the controller at boot-up are default values, and hence, they are ignored +in favor of a non-zero value. +

Previous releases of the motor record allowed the auto restore to take +precedence over the controller when initializing the target position (i.e., +DVAL). +
  +

+Access Security Level

+In order to support the new VMAX/SMAX fields, the Access Security Level +for the following fields have been changed from one to zero: +
+
    +
  • +MRES
  • + +
  • +UREV
  • + +
  • +VBAS
  • + +
  • +SBAS
  • +
+
+ +

+MM4000/5 Device Driver

+Although the previous motor record release (V3.5) had device driver support +for the MM4000, it is not recommended for use with either the MM4000 +or the MM4005 controllers.  The following changes were made to the +previous release to create, what is now, the MM4000/5 device driver support: +
    +
  1. +The MM4000 device driver software supports both the MM4000 and MM4005 controllers.  +Driver level software detects and saves which controller it is communicating +with at boot-up.  Currently, there are two functional differences  +between the two models.
  2. + +
      +
    1. +The MM4005's position cannot be set by a host.  This mean that, for +the MM4005 only, setting the motor record RVAL or DVAL fields has no effect.
    2. + +
    3. +Since the MM4005's position cannot be set by EPICS, the initial position +of its' motors (see Initial Position above) will always be initialized +from the controller's value.
    4. +
    + +
  3. +The MM4005 supports up to eight axes.  User access to the controller's +front panel is required to scroll the front panel display through all eight +axes.  Since remote mode locks out the user from using the +controller's front panel, the motor record no longer puts the MM4000/5 +in remote mode.  EPICS is unable to communicate with the MM4000/5 +controller if it is in local mode and the front panel is +not at the top menu.   A Controller communication error +bit was allocated in the MSTA field to help aid user's in diagnosing a +controller communication error.  Currently, only the MM4000/5 device +driver sets this error indicator.  When the communication error bit +is set True in the MSTA, the motor record SEVR field is set to INVALID_ALARM +and the STAT field is set to COMM_ALARM.  User's who want their MM4000/5 +in remote mode at boot-up can add the remote mode command ("MR") to  +their INIT field.
  4. +
+ +

+Bug Fix for OMS VME58 running on PowerPC

+Through an odd set of circumstances the Oms58 driver was not performing +status updates on PowerPC (PPC) platforms.  All users of the OMS VME58 +controller on a PPC platform must upgrade to Motor Record version 4.0 for +full functionality. +
+

+New Features

+Newport MM3000 Device Support +

Device driver support for the MM3000 exist for this release of the motor +record.  Note the following differences between the MM4000 and MM3000 +device drivers: +

    +
  • +The MM3000 does not support a generic Load Position commands.  +In other words, the user can only define the current controller position +as being the zero position.  This limitation is reflected in the motor +record device support.  When the SET field is true, the only valid +entry to either the DVAL or RVAL fields is zero.
  • + +
  • +The only position units the MM3000 will communicate with the host in, are +either encoder ticks or stepper motor steps.
  • +
+ +

+Uninitialized Oms/Oms58 Driver Error Check

+With previous release, if the Oms or Oms58 driver was some how omitted +from the database, a "... card does not exist" error message would result.  +With this release, an explicit error check and corresponding error message +(i.e., "Oms[58] driver uninitialized.") is issued at record initialization +if a required driver is not initialized. (Because of the particulars of +MM[3000/4000] initialization, this is not an issue with Newport controllers.) +

+Maximum Velocity Fields (VMAX/SMAX)

+Maximum velocity fields have been added; VMAX in EGU/s units and SMAX in +RPS units. +

In order to support BURT, range checking is done in such a way that +any minimum (i.e., VBAS/SBAS) or maximum (i.e., VMAX/SMAX) value entered +is valid.  For example, if the minimum is entered and it exceeds the +maximum, then the maximum is set to the new minimum value.  Slew (VELO/S) +and backup velocity (BVEL/SBAK) fields are forced by the motor record to +be within the range set by VMAX/SMAX and VBAS/SBAS, inclusively.  +For example, if VELO is entered and it is less than the minimum, then VELO +is set to VBAS. +

To facilitate software upgrades, a zero VMAX disables maximum velocity +error checking.  Those who use both BURT and VMAX (i.e., nonzero VMAX) +should insure that VMAX and VBAS are placed before VELO and BVEL in their +BURT request files.  VMAX/SMAX have Access Security Level zero. +

motorx_setup_1.7.adl (which is included with this distribution) +includes support for the maximum velocity fields. +
  +

+

+Motor Record Version 3.5 Release Notice

+Release 3.5 of the motor record is only compatible with EPICS baseR3.13.1.1 +and above.  This document describes changes made to the motor record +and its' associated device driver support between versions 3.4 and 3.5.  +Those changes are as follows: +
+

+Modifications to Existing Features

+ +

+Command Primitives

+This feature is available only with OMS VME8/44/58 or Newport MM4000 +device support (i.e., devOms, devOms58, devMM4000). Three motor record +fields are available to the user to send ASCII command primitives to the +servo control board at fixed, predefined, times.  Each of the following +fields is defined as a character string: +
  1. INIT - Sent at record initialization. +
  2. PREM - Sent before every command string that causes motion. +
  3. POST - Sent after a complete motion is finished or when +an overtravel limit switch is detected.
+No error checking is done by the motor record or the device driver to insure +that the command strings are valid.  Command primitives that result +in a response from the motion control board are valid, but the response +is not processed. +

+Servo related Fields

+ +
    +
  • +The GAIN field "prompt" has been changed from "Response Gain" to "Combined +Gain".  In addition, valid values for the GAIN field have been changed +to [0.0, for MM4000 - 0.00005, for OMS] <= GAIN <= 1.  For more +on PID, see "PID Gain Parameters" below.
  • + +
  • +The status enable field (i.e., STEN) has been eliminated; the control +enable field (i.e., CNEN) is used to both control the torque enable +and show its' status.
  • +
+ +

+Unused Fields Removed

+The following unused motor record fields were deleted: MODE, TRAK, MDEL, +ADEL, CVEL, POSM, ALST, MLST +
  +
  +
+

+New Features

+ +

+Device Directives

+Device directive definition and processing: +
    +
  • +Valid only in the INIT field.
  • + +
  • +Must be identified by the following;
  • + +
    +
  • +First character of INIT string must be a '@'.
  • + +
  • +One or more characters followed by a terminating '@'; i.e., device directives +must have nonzero length.
  • + +
  • +A valid device directive; currently, only "DPM_ON".
  • +
    + +
  • +INIT strings are stripped of valid device directives (including @'s) and +tested for nonzero length before being sent to the controller.  For +example, given the INIT string, "@DPM_ON@HE", the device directive @DPM_ON@ +is stripped out before HE is sent to the controller.
  • +
+ +

+Driver Power Monitoring

+ +
    +
  • +This feature is only available with the OMS VME58 device support.
  • + +
  • +The 8 User I/O signals are assigned to the 8 possible VME58 axes as follows:
  • +
+ +
                    +User I/O #0 <> X axis +
                    +"       " 1 <> Y  " +
                    +..................... +
                    +"       " 7 <> S  " +
  • +Drive-power monitoring defaults to disabled at boot-up.  Request enabling +drive-power monitoring by entering the device directive "@DPM_ON@" command +into the motor record initialization field (i.e., INIT).  The INIT +field is processed at record initialization (i.e., bootup), hence if there +are no errors, drive-power monitoring will be enabled after the next bootup.
  • + +
  • +Whenever a request is made to enable drive-power status monitoring, an +error check is made (using the VME58 "RB" command) to verify that the User +I/O has been configured as an input.  The following message will appear +in the error log if a configuration error is detected; "Invalid VME58 configuration; +RB = 0x####", where "####" is the VME58's response to the RB command.
  • + +
  • +When drive-power status monitoring is enabled and a power failure is detected, +the device driver will respond by activating the the RA_OVERTRAVEL bit +in the MSTA.  This results in either HLS or LLS being activated depending +on the DIR field. In addition, the following message will appear in the +error log; "Drive power failure at VME58 card#?? motor#??".
  • +
+ +

+Soft Channel Device Support

+The immediate goals for soft channel motor record device support were twofold.  +First, to solve the problem of nonlinear position conversion in the data +base, rather than in the record.  Second, to provide a more flexible +motor record interface for Table Records and SPEC. +

New fields have been added to the motor record to support the Soft Channel +device driver.   The new fields are all database links associated +with existing motor record fields.  The new links and their associated +fields are listed in the table below: +
  +
  +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
LinkLink Type Associated Field
OUT*DBOUTPUT +
DVAL
+
STOODBOUTPUTSTOP
DINPDBINPUTDMOV
RDBL*DBINPUT +
DRBV
+
RINPDBINPUT +
RMP
+
+ +
+

* - Not a new field, but a new function provided only by soft channel +device support.

+ +

The Soft Channel database links are only processed when the Soft Channel +device driver is selected.  These links are ignored when using any +other Motor Record device driver.  At this time, the above links are +not +dynamically retargetable. +

The OUT, DINP and RDBL are monitored for value changes via a CA event +task.  Users must choose either a dial input link (RDBL) or a raw +input link (RINP), but not both. +
  +
  +

+Newport MM4000 Device Support

+This is Mark +Rivers MM4000 device support ported to the latest motor record release.  +The following are the functional differences between Mark's version 1.01 +and +
this release: +
+
    +
  1. +When either user or dial travel limit values are entered, a validity check +is made using the travel limit values from the MM4000 control.  User +and dial travel limit values are valid if they are within the travel limits +set on the front panel of the MM4000 controller.  Attempting to enter +a travel limit outside the MM4000 controller's range results in the travel +limit being reset to the MM4000's value.
  2. + +
  3. +Servo support has been extended to the MM4000 controller.
  4. +
+
+ +

+PID Gain Parameters

+With this release there are two ways to set the motor controllers' PID +parameters; either through the Combined Gain field (i.e., GAIN) +or through the individual PID gain parameter fields (i.e., PCOF, ICOF, +DCOF).  Let the motor controller PID parameters be represented by +CKP, CKI and CKD; then the relationship between these two methods is as +follows: +
For the MM4000              +For all OMS controllers
+ +
    +
  • +CKP  = GAIN                              +CKP =        1999.9 * GAIN
  • + +
  • +CKI   = 2 * GAIN                        +CKI =  2 * 1999.9 * GAIN
  • + +
  • +CKD = 3 * GAIN                        +CKD = 3 * 1999.9 * GAIN
  • +
+Note that setting any of the individual PID record fields is not +reflected in the value of the GAIN field. +

+Highland V544 Device Support

+Device support for the Highland V544 is available with this release.  +This version (i.e., version 1.3) is functionally the same as the earlier +release (i.e., version 1.2).  No new features have been added. +
  +
+

+Motor Record Version 3.4 Release Notice

+This document describes changes made to the motor record and its' associated +device driver support between versions 3.3 and 3.4.  Those changes +are as follows: +

+Device driver design error fix

+A design error was discovered in the OMS device drivers (drvOms and drvOms58).  +The EPICS device driver support task (i.e., tmotor) would query the OMS +motion controller for status information immediately after a motion command.  +Since the state of the controller board was in the midst of changing, this +sometimes resulted in inconsistent or conflicting status information being +returned to the motor record.  This problem was remedied by enforcing +a minimum time delay (16.67 ms) between a motion command and a status query. +

It is difficult to enumerate the symptoms associated with this problem.  +Sometimes they exhibited themselves intermittently, other times bad data +stayed in the record.   Several symptoms are as follows: +

+
    +
  • +Erroneous motor stops being issued by the device support when changing +motor direction.
  • + +
  • +Backlash occurring when the motor is moving in the same direction as the +sign of BDST.
  • + +
  • +DVAL and RBV becoming out-of-synch.  RBV would always be an old value +from the previous move.
  • +
+
+ +

+PID Parameter Support for VME58

+The motor record classifies a motor as a servo if it has two features; +an adjustable closed loop position response gain and the ability to enable/disable +closed loop position control (i.e., motor torque).  A motor lacking +either of the above two features is classified as a stepper and is supported +by the standard motor record features.  The following three motor +record fields support servo motors: +

  1. GAIN - Closed loop position response gain of the motor. +
  2. CNEN - Enable/disable closed loop position control request. +
  3. STEN - Closed loop position control status (i.e., +
     enabled/disabled). +

Currently, servo support is only available with OMS's VME58 device support +(i.e., VME58-[4S/8S/2S2/2S6/4S4/6S2] model boards).  At driver initialization, +the driver automatically detects whether or not the underlying device supports +the use of the GAIN field and thereby classifies the motor as a stepper +or a servo.   Bit#11 of the MSTA field is set on/off based on +the results of this test.  If the device supports the GAIN field, +then bit#11 of the MSTA is set on and all of the above servo fields are +enabled.   Otherwise, they are disabled and bit#11 of the MSTA +is set off.  When the servo fields are disabled, they can still be +read or written to without an error response. +

The VME58 device support allows the closed loop position gain of the +motor to be set directly through the GAIN field.  For the VME58, setting +the GAIN field value results in the Combined Coefficient command (i.e., +KK#) being executed with the GAIN field value as the argument to the command.  +This command, in turn, sets the PID loop parameter values (with the OMS +VME58, gain changes do not take effect until the command velocity is zero). +

The user can request that the closed loop position control be enabled +or disabled by setting the CNEN field nonzero or zero, respectively. +
Likewise, the user can monitor the state of closed loop position control +(i.e., enabled/disabled) by reading the STEN field.  See the OMS "VME58 +Family User's Manual" for further details. +

+Command primitives feature

+Three motor record fields are available to the user that can be used to +send ASCII command primitives to the servo control board at fixed, predefined, +times.  Each field is defined as a character string. +

  1. INIT - Sent at record initialization. +
  2. PREM - Sent before every command string that causes motion. +
  3. POST - Sent after a complete motion is finished. +

No error checking is done by the motor record or the device driver to +insure that the command strings are valid.  Command primitives that +result in a response from the motion control board are valid, but the response +is not processed. +
  +
  + + diff --git a/iocBoot/Makefile b/iocBoot/Makefile new file mode 100644 index 00000000..d64511f3 --- /dev/null +++ b/iocBoot/Makefile @@ -0,0 +1,4 @@ +TOP = .. +include $(TOP)/config/CONFIG_APP +DIRS += $(wildcard *ioc*) +include $(TOP)/config/RULES.iocBoot diff --git a/iocBoot/iocmotor/Makefile b/iocBoot/iocmotor/Makefile new file mode 100644 index 00000000..c4ef70be --- /dev/null +++ b/iocBoot/iocmotor/Makefile @@ -0,0 +1,4 @@ +TOP = ../.. +include $(TOP)/config/CONFIG_APP +ARCH = mv167 +include $(TOP)/config/RULES.ioc diff --git a/iocBoot/iocmotor/st.cmd b/iocBoot/iocmotor/st.cmd new file mode 100644 index 00000000..672d6d38 --- /dev/null +++ b/iocBoot/iocmotor/st.cmd @@ -0,0 +1,20 @@ +# Example vxWorks startup file +#Following must be added for many board support packages +#cd + +< cdCommands + +#< ../nfsCommands + +cd appbin +ld < iocCore +ld < seq +#ld < Lib + +cd startup +#dbLoadDatabase("../../dbd/.dbd") +#dbLoadRecords("../../db/.db") +#dbLoadTemplate("../../db/.substitutions") + +iocInit +#seq & diff --git a/iocBoot/nfsCommands b/iocBoot/nfsCommands new file mode 100644 index 00000000..acafd577 --- /dev/null +++ b/iocBoot/nfsCommands @@ -0,0 +1,28 @@ +#Instructions for creating and using a real nfsCommands file +# +# in order to use nfs do the following: +# 1) Create hostAdd and nfsMountAll commands for each nfs server +# 2) In each st.cmd file add the following two commands BEFORE any load commands +# ../nfs.cmd +# cd " +# +# The hostAdd and nfsMountAll commands have the form: +# hostAdd("","xxx.xxx.xxx.xxx") +# nfsMountAll("") +# +# You can also mount subdirectories as follows: +# nfsMount("", "/xxx/xxx/xxx", "/xxx") +# +# For example assume +# +# host is mercury with inet address 155.77.2.56 +# You want to mount the directory (which is a file system of mercury) +# /home/mercury5/iocinfo +# as +# /iocinfo +# +# The commands would be +# +# hostAdd("mercury","155.77.2.56") +# nfsMountAll("mercury") +# nfsMount("mercury","/home/mercury5/iocinfo","/iocinfo") diff --git a/motorApp/Db/Makefile b/motorApp/Db/Makefile new file mode 100644 index 00000000..0380f4bf --- /dev/null +++ b/motorApp/Db/Makefile @@ -0,0 +1,3 @@ +TOP=../.. +include $(TOP)/config/CONFIG_APP +include $(TOP)/config/RULES_ARCHS diff --git a/motorApp/Db/Makefile.Host b/motorApp/Db/Makefile.Host new file mode 100644 index 00000000..529a3d1c --- /dev/null +++ b/motorApp/Db/Makefile.Host @@ -0,0 +1,30 @@ +# Makefile.Host +TOP = ../../.. +include $(TOP)/config/CONFIG_APP +#---------------------------------------- +# ADD MACRO DEFINITIONS AFTER THIS LINE + +#---------------------------------------------------- +# Optimization of DB files using dbst (default: NO) +#DB_OPT = YES + +#---------------------------------------------------- +# Optimization needs the name of the appropriate +# dbd file (compare with ../src/Makefile.Vx) +#DBDNAME = motor.dbd + +#---------------------------------------------------- +# Add databases, templates, substitutions like this +#DB += motor.db + +#---------------------------------------------------- +# Declare template files which do not show up in DB +#USES_TEMPLATE += motor.template + +#---------------------------------------------------- +# The following lines are for local dbd expansion +#DBDEXPAND = motorInclude.dbd + +include $(TOP)/config/RULES.Db +#---------------------------------------- +# ADD RULES AFTER THIS LINE diff --git a/motorApp/Makefile b/motorApp/Makefile new file mode 100644 index 00000000..5692fbe3 --- /dev/null +++ b/motorApp/Makefile @@ -0,0 +1,16 @@ +TOP = .. +include $(TOP)/config/CONFIG_APP + +# MotorSrc is required for all motor record configurations. +# It MUST appear 1st in this list of directories. +DIRS += MotorSrc + +# Individual device/driver support modules may be commented +# out here. +DIRS += NewportSrc +DIRS += OmsSrc +DIRS += SoftMotorSrc +DIRS += V544Src + +DIRS += Db +include $(TOP)/config/RULES_DIRS diff --git a/motorApp/MotorSrc/Makefile b/motorApp/MotorSrc/Makefile new file mode 100644 index 00000000..0380f4bf --- /dev/null +++ b/motorApp/MotorSrc/Makefile @@ -0,0 +1,3 @@ +TOP=../.. +include $(TOP)/config/CONFIG_APP +include $(TOP)/config/RULES_ARCHS diff --git a/motorApp/MotorSrc/Makefile.Host b/motorApp/MotorSrc/Makefile.Host new file mode 100644 index 00000000..b1831c5f --- /dev/null +++ b/motorApp/MotorSrc/Makefile.Host @@ -0,0 +1,14 @@ +# Makefile.Host +TOP = ../../.. +include $(TOP)/config/CONFIG_APP +#---------------------------------------- +# ADD MACRO DEFINITIONS AFTER THIS LINE + +RECTYPES += motorRecord.h + +INC += motor.h motordevCom.h motordrvCom.h +INC += motordrvComCode.h + +include $(TOP)/config/RULES.Host +#---------------------------------------- +# ADD RULES AFTER THIS LINE diff --git a/motorApp/MotorSrc/Makefile.Vx b/motorApp/MotorSrc/Makefile.Vx new file mode 100644 index 00000000..25bd273d --- /dev/null +++ b/motorApp/MotorSrc/Makefile.Vx @@ -0,0 +1,18 @@ +# Makefile.Vx +TOP = ../../.. +include $(TOP)/config/CONFIG_APP +#---------------------------------------- +# ADD MACRO DEFINITIONS AFTER THIS LINE + +# The following is required for all motor record configurations. +SRCS.c += ../motorRecord.c ../motordevCom.c ../motordrvCom.c +LIBOBJS = motorRecord.o motordevCom.o motordrvCom.o + +LIBNAME = MotorLib + +#Note that the command line that builds the +#library $(LIBNAME) may be HUGE (>3kB) +# +include $(TOP)/config/RULES.Vx +#---------------------------------------- +# ADD RULES AFTER THIS LINE diff --git a/motorApp/MotorSrc/motor.h b/motorApp/MotorSrc/motor.h new file mode 100644 index 00000000..e21848a7 --- /dev/null +++ b/motorApp/MotorSrc/motor.h @@ -0,0 +1,156 @@ +/* +FILENAME... motor.h +USAGE... Definitions and structures common to all levels of motorRecord + support (i.e., record, device and driver). + +Version: $Revision: 1.1 $ +Modified By: $Author: sluiter $ +Last Modified: $Date: 2000-02-08 22:18:42 $ +*/ + +/* + * Current Author: Ron Sluiter + * Date: 12/22/98 + * + * Experimental Physics and Industrial Control System (EPICS) + * + * Copyright 1991, the Regents of the University of California, + * and the University of Chicago Board of Governors. + * + * This software was produced under U.S. Government contracts: + * (W-7405-ENG-36) at the Los Alamos National Laboratory, + * and (W-31-109-ENG-38) at Argonne National Laboratory. + * + * Initial development by: + * The Controls and Automation Group (AT-8) + * Ground Test Accelerator + * Accelerator Technology Division + * Los Alamos National Laboratory + * + * Co-developed with + * The Controls and Computing Group + * Accelerator Systems Division + * Advanced Photon Source + * Argonne National Laboratory + * + * Modification Log: + * ----------------- + */ + +#ifndef INCmotorh +#define INCmotorh 1 + +#include +#include +#include + +/* Maximum message size of all supported devices; see drv[device].h for maximum +message size for each device. */ +#define MAX_MSG_SIZE 300 + +typedef enum BOOLEAN_VALUES {OFF = 0, ON = 1} BOOLEAN; + +#define NINT(f) (long)((f)>0 ? (f)+0.5 : (f)-0.5) /* Nearest integer. */ + +/* Motor Record Command Set. !WARNING! this enumeration must match the following; + - "oms_table" in devOmsCom.c + - "MM4000_table" in devMM4000.c +*/ + +enum motor_cmnd { + MOVE_ABS, /* Absolute Move. */ + MOVE_REL, /* Relative Move. */ + HOME_FOR, /* Home Forward. */ + HOME_REV, /* Home Reverse. */ + LOAD_POS, /* Load Zero Position. */ + SET_VEL_BASE, /* Set Minimum Velocity. */ + SET_VELOCITY, /* Set Jog and Trajectory Velocity. */ + SET_ACCEL, /* Set Acceleration. */ + GO, /* Start previously programmed move. */ + SET_ENC_RATIO, /* Set Encoder Ratio. */ + GET_INFO, /* Update Motor Status. */ + STOP_AXIS, /* Stop Axis Motion. */ + JOG, /* Momentary Jog. */ + SET_PGAIN, /* Set Proportional Gain. */ + SET_IGAIN, /* Set Integral Gain. */ + SET_DGAIN, /* Set Derivative Gain. */ + ENABLE_TORQUE, /* Enable Servo Closed-Loop Control. */ + DISABL_TORQUE, /* Disable Servo Closed-Loop Control. */ + PRIMITIVE, /* Primitive Controller command. */ + SET_HIGH_LIMIT, /* Set High Travel Limit. */ + SET_LOW_LIMIT /* Set Low Travel Limit. */ +}; + +#ifndef __cplusplus +typedef enum motor_cmnd motor_cmnd; +#endif + +/* -------------------------------------------------- */ + +/* driver and device support parameters */ +#define SCAN_RATE 6 /* 60=once a second */ +#define MAX_COUNT 50000 /*19000*/ /* timeout value */ +#define MAX_AXIS 10 /* max number of axis per board */ + +#define NOTHING_DONE 0 +#define CALLBACK_DATA 1 + +#define NO 0 +#define YES 1 + +/* -------------------------------------------------- */ +/* axis and encoder status for return to requester */ +#define RA_DIRECTION 0x01 /* (last) 0=Negative, 1=Positive */ +#define RA_DONE 0x02 /* a motion is complete */ +#define RA_OVERTRAVEL 0x04 /* a limit switch has been hit */ +#define RA_HOME 0x08 /* The home signal is on */ +#define EA_SLIP 0x10 /* encoder slip enabled */ +#define EA_POSITION 0x20 /* position maintenence enabled */ +#define EA_SLIP_STALL 0x40 /* slip/stall detected */ +#define EA_HOME 0x80 /* encoder home signal on */ +#define EA_PRESENT 0x100 /* encoder is present */ +#define RA_PROBLEM 0x200 /* driver stopped polling */ +#define RA_MOVING 0x400 /* non-zero velocity present */ +#define GAIN_SUPPORT 0x800 /* Motor supports closed-loop position control. */ +#define CNTRL_COMM_ERR 0x1000 /* Controller communication error. */ + +/* + The RA_PROBLEM status bit indicates that the driver has stopped the polling + because it believes that the motor is not really moving. Under normal + operation, the controller will tell the driver when the motor is done + moving. This is a safety precaution and should normally be treated like a + RA_DONE. +*/ + +#ifdef __cplusplus +struct local_dset +{ + long number; /*number of support routines*/ + long (*report) (FILE, int); /*print report*/ + long (*init) (int); /*init support*/ + long (*init_record) (void *); /*init support for particular record*/ + long (*get_ioint_info) + (int, struct dbCommon *, IOSCANPVT *); /* get io interrupt information*/ +}; +#endif + +/* device support entry table */ +struct motor_dset +{ +#ifdef __cplusplus + struct local_dset base; + long (*update_values) (struct motorRecord *); + long (*start_trans) (struct motorRecord *); + long (*build_trans) (motor_cmnd, double *, struct motorRecord *); + long (*end_trans) (struct motorRecord *); +#else + struct dset base; + DEVSUPFUN update_values; + DEVSUPFUN start_trans; + DEVSUPFUN build_trans; + DEVSUPFUN end_trans; +#endif +}; + +#endif /* INCmotorh */ + diff --git a/motorApp/MotorSrc/motorRecord.c b/motorApp/MotorSrc/motorRecord.c new file mode 100644 index 00000000..b8baf58b --- /dev/null +++ b/motorApp/MotorSrc/motorRecord.c @@ -0,0 +1,3265 @@ +/* +FILENAME... motorRecord.c +USAGE... Record Support Routines for the Motor record. + +Version: $Revision: 1.1 $ +Modified By: $Author: sluiter $ +Last Modified: $Date: 2000-02-08 22:18:43 $ +*/ + +/* + * Original Author: Jim Kowalkowski + * Current Author: Tim Mooney + * Date: 06/14/95 + * + * Experimental Physics and Industrial Control System (EPICS) + * + * Copyright 1995, 1996 the University of Chicago Board of Governors. + * + * This software was produced under U.S. Government contract: + * (W-31-109-ENG-38) at Argonne National Laboratory. + * + * Developed by + * The Beamline Controls and Data Acquisition Group + * Experimental Facilities Division + * Advanced Photon Source + * Argonne National Laboratory + * + * Co-developed with + * The Controls and Computing Group + * Accelerator Systems Division + * Advanced Photon Source + * Argonne National Laboratory + * + * + * Modification Log: + * ----------------- + * .01 01-18-93 jbk initial development + * .02 01-25-93 tmm initial development continued + * .03 09-13-93 tmm disabled HOPR & LOPR with #define USE_HOPR_LOPR 0 + * .04 08-01-94 tmm move to 3.11.6 (tsLocalTime -> recGblGetTimeStamp) + * .05 09-07-94 tmm make .diff zero when we set drive fields to readback + * fields + * .06 09-07-94 tmm test for stop and spmg every time record processes + * .07 11-15-94 jps create .rvel to indicate motor actually moving + * .08 06-12-95 tmm added VERSION and .vers field, fixed stuck .dmov flag + * .09 06-14-95 tmm propagate dial-value calibration to user value; + * fix precision and units assoc. with .vers field + * .10 07-20-95 tmm added record name to debug output + * .11 07-20-95 tmm check that we've actually moved before worrying whether + * we're going in the wrong direction + * .12 08-23-95 tmm version 1.5 removed HOPR/LOPR code, added dial dir + * (sign of .mres). Fixed coding error in jog*. No + * longer propagate dial-value calib. to user value + * .13 08-25-95 tmm version 1.51 limit switches now reported w.r.t user + * coordinates. Added raw limit switches .rhls, .rlls. + * .14 08-28-95 tmm version 1.6 Added .card field, so users can know which + * card in crate is associated with this motor. + * .15 08-30-95 tmm version 1.7 Added .nmap field to supplement .mmap, and + * rewrote MARK() macros. + * .16 08-31-95 tmm version 1.8 Freeze-offset switch (.foff) replaces earlier + * "propagate dial-value calibration to user value" + * .17 09-01-95 tmm limit-violation changes. + * .18 09-07-95 tmm version 1.9 Added .fof / .vof fields to put .foff (freeze + * offset) into frozen / variable mode. + * Added .urev, .srev, .s, .sbas, .sbak fields to meet user + * demands for more intuitive setting of speeds and + * resolution. + * Modified offset handling. Changing .off should never + * cause the motor to move. + * .19 09-08-95 tmm version 1.91 Work around 8-character limit in handling + * by dbGet() of the string returned by get_units(). + * .20 09-13-95 tmm version 1.92 Allow .rdbl (readback inlink) and .rlnk + * (readout outlink) to be channel-access links. + * .21 09-26-95 tmm version 1.93 Post process (as if STOP had been hit) + * when a limit switch is hit. When post process sets, + * e.g., VAL=RBV, then also set LVAL=RBV. + * .22 11-21-95 tmm Version 1.94 Fix post_MARKed_fields. Fix pmr->pp bug + * that caused post processing every time. + * .23 12-27-95 tmm Version 1.95 Implemented MISS field. More sanity checks + * on resolution, speed, and acceleration. + * .24 02-09-96 tmm Version 1.96 Don't db_post dmov==0 if special() already + * posted it. + * .25 03-19-96 tmm v1.97 Much more precise encoder-ratio calculation. + * Made actual home speed independent of UEIP flag. + * (Thanks to Vicky Austin, Royal Greenwich Observatory.) + * .26 06-07-96 tmm v1.98 Added more debugging output. + * .27 06-10-96 tmm v2.00 Fixed the way changes to resolutions and speeds are + * handled in init_record() and in special(). Now, when + * motor resolution is changed, speeds in revs/sec are held + * constant, which requires speeds in EGUs/sec to change. + * .28 08-02-96 tmm v2.1 Changing mres or eres while in SET mode no longer + * changes the raw motor position, but instead changes user + * and dial positions to agree with the old raw position. + * .29 08-09-96 tmm v2.2 Changed the order in which PV's are posted. DMOV is + * last; frequently changed PV's are first. + * .30 08-14-96 tmm v2.3 Fixed long-standing bug in special handling of DVAL, + * RVAL, RLV. + * .31 08-14-96 tmm v3.0 Conversion to EPICS 3.13.0 + * .32 12-16-96 tmm v3.1 Fixed moving-in-right-dir check, and jog, when using + * relative moves. Fixed JOG limits. + * .33 05-29-97 tmm v3.2 Watchdog callback for encoder-settling time delay. + * Added DLY field to implement delay. + * Split postProcess() and maybeRetry() out of process(). + * Fix problem of first call to process() looking like a + * device-support callback (first move command fails). + * Don't do mid-course checks if using encoder or readback + * device. (We don't know how badly they might behave while + * the motor's moving.) Set .res field in init_record(). + * Set retry deadband according to .mres, .eres, and .bdst. + * .34 05-30-97 tmm v3.3 Relative moves know about motor granularity. + * .35 10-11-99 rls v4.0 Enforce finite state machine on jog request, jogging, + * stopping a jog and backlash after jogging. These + * modifications fix the "runaway jog" problem discovered + * with the MM4000 device support using serial communication + * when the user "hammers" on the jog request. + */ + +#define VERSION 4.1 + +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#include +#include +#include +#include +#include +} +#else +#include +#include +#include +#include +#include +#endif + +#include +#include +#include +#include + +#define GEN_SIZE_OFFSET +#ifdef __cplusplus +extern "C" { +#include "motorRecord.h" +} +#else +#include "motorRecord.h" +#endif +#undef GEN_SIZE_OFFSET + +#include "motor.h" + + +#define STATIC static +#define MAX(a,b) ((a)>(b)?(a):(b)) /* nearest integer */ + +/*** Forward references ***/ + +STATIC long do_work(motorRecord *); +STATIC void alarm(motorRecord *); +STATIC void monitor(motorRecord *); +STATIC void post_MARKed_fields(motorRecord *, unsigned short); +STATIC void process_motor_info(motorRecord *); +STATIC void load_pos(motorRecord *); +STATIC void check_speed_and_resolution(motorRecord *); +STATIC void set_dial_highlimit(motorRecord *, struct motor_dset *); +STATIC void set_dial_lowlimit(motorRecord *, struct motor_dset *); +STATIC void set_userlimits(motorRecord *); +STATIC void range_check(motorRecord *, float *, double, double); + +/*** Record Support Entry Table (RSET) functions. ***/ + +STATIC long init_record(motorRecord *, int); +STATIC long process(motorRecord *); +STATIC long special(struct dbAddr *, int); +STATIC long get_units(struct dbAddr *, char *); +STATIC long get_precision(struct dbAddr *, long *); +STATIC long get_graphic_double(struct dbAddr *, struct dbr_grDouble *); +STATIC long get_control_double(struct dbAddr *, struct dbr_ctrlDouble *); +STATIC long get_alarm_double(struct dbAddr *, struct dbr_alDouble *); + +/* record support entry table */ +#ifdef __cplusplus +struct local_rset +{ + long number; /*number of support routines*/ + long (*report) (FILE, int); /*print report*/ + long (*init) (void); /*init support*/ + long (*init_record) (void *, int); /*init support for particular record*/ + long (*process) (void *); + long (*special) (struct dbAddr *, int); + long (*get_value) (void *, struct valueDes *); + long (*cvt_dbaddr) (struct dbAddr *); + long (*get_array_info) (struct dbAddr *, long *, long *); + long (*put_array_info) (struct dbAddr *, long); + long (*get_units) (struct dbAddr *, char *); + long (*get_precision) (struct dbAddr *, long *); + long (*get_enum_str) (struct dbAddr *, char *); + long (*get_enum_strs) (struct dbAddr *, struct dbr_enumStrs *); + long (*put_enum_str) (struct dbAddr *, char *); + long (*get_graphic_double) (struct dbAddr *, struct dbr_grDouble *); + long (*get_control_double) (struct dbAddr *, struct dbr_ctrlDouble *); + long (*get_alarm_double) (struct dbAddr *, struct dbr_alDouble *); +}; +#endif + +#ifdef __cplusplus +struct local_rset motorRSET = +#else +struct rset motorRSET = +#endif +{ + RSETNUMBER, + NULL, + NULL, + init_record, + process, + special, + NULL, + NULL, + NULL, + NULL, + get_units, + get_precision, + NULL, + NULL, + NULL, + get_graphic_double, + get_control_double, + get_alarm_double +}; + + +/******************************************************************************* +Support for tracking the progress of motor from one invocation of 'process()' +to the next. The field 'pmr->mip' stores the motion in progress using these +fields. ('pmr' is a pointer to motorRecord.) +*******************************************************************************/ +#define MIP_DONE 0x0000 /* No motion is in progress. */ +#define MIP_JOGF 0x0001 /* A jog-forward command is in progress. */ +#define MIP_JOGR 0x0002 /* A jog-reverse command is in progress. */ +#define MIP_JOG_BL 0x0004 /* Done jogging; now take out backlash. */ +#define MIP_JOG (MIP_JOGF | MIP_JOGR | MIP_JOG_BL) +#define MIP_HOMF 0x0008 /* A home-forward command is in progress. */ +#define MIP_HOMR 0x0010 /* A home-reverse command is in progress. */ +#define MIP_HOME (MIP_HOMF | MIP_HOMR) +#define MIP_MOVE 0x0020 /* A move not resulting from Jog* or Hom*. */ +#define MIP_RETRY 0x0040 /* A retry is in progress. */ +#define MIP_LOAD_P 0x0080 /* A load-position command is in progress. */ +#define MIP_LOAD_ER 0x0100 /* A load-encoder-ratio command in progress. */ +#define MIP_STOP 0x0200 /* We're trying to stop. When combined with */ +/* MIP_JOG* or MIP_HOM*, the jog or home */ +/* command is performed after motor stops */ +#define MIP_DELAY_REQ 0x0400 /* We set the delay watchdog */ +#define MIP_DELAY_ACK 0x0800 /* Delay watchdog is calling us back */ +#define MIP_DELAY (MIP_DELAY_REQ | MIP_DELAY_ACK) /* Waiting for readback + * to settle */ +#define MIP_JOG_REQ 0x1000 /* Jog Request. */ +#define MIP_JOG_STOP 0x2000 /* Stop jogging. */ + +/******************************************************************************* +Support for keeping track of which record fields have been changed, so we can +eliminate redundant db_post_events() without having to think, and without having +to keep lots of "last value of field xxx" fields in the record. The idea is +to say... + + MARK(M_XXXX); + +when you mean... + + db_post_events(pmr, &pmr->xxxx, monitor_mask); + +Before leaving, you have to call post_MARKed_fields() to actually post the +field to all listeners. monitor() does this. + + --- NOTE WELL --- + The macros below assume that the variable "pmr" exists and points to a + motor record, like so: + motorRecord *pmr; + No check is made in this code to ensure that this really is true. +*******************************************************************************/ +/* Bit field for "mmap". */ +typedef union +{ + unsigned long All; + struct + { + unsigned long M_VAL :1; + unsigned long M_DVAL :1; + unsigned long M_HLM :1; + unsigned long M_LLM :1; + unsigned long M_DMOV :1; + unsigned long M_SPMG :1; + unsigned long M_RCNT :1; + unsigned long M_MRES :1; + unsigned long M_ERES :1; + unsigned long M_UEIP :1; + unsigned long M_URIP :1; + unsigned long M_LVIO :1; + unsigned long M_RVAL :1; + unsigned long M_RLV :1; + unsigned long M_OFF :1; + unsigned long M_RBV :1; + unsigned long M_DHLM :1; + unsigned long M_DLLM :1; + unsigned long M_DRBV :1; + unsigned long M_RDBD :1; + unsigned long M_MOVN :1; + unsigned long M_HLS :1; + unsigned long M_LLS :1; + unsigned long M_RRBV :1; + unsigned long M_RMP :1; + unsigned long M_REP :1; + unsigned long M_MSTA :1; + unsigned long M_ATHM :1; + unsigned long M_TDIR :1; + unsigned long M_MIP :1; + unsigned long M_DIFF :1; + unsigned long M_RDIF :1; + } Bits; +} mmap_field; + +/* Bit field for "nmap". */ +typedef union +{ + unsigned long All; + struct + { + unsigned long M_S :1; + unsigned long M_SBAS :1; + unsigned long M_SBAK :1; + unsigned long M_SREV :1; + unsigned long M_UREV :1; + unsigned long M_VELO :1; + unsigned long M_VBAS :1; + unsigned long M_BVEL :1; + unsigned long M_MISS :1; + unsigned long M_ACCL :1; + unsigned long M_BACC :1; + } Bits; +} nmap_field; + + +#define MARK(FIELD) {mmap_field temp; temp.All = pmr->mmap; \ + temp.Bits.FIELD = 1; pmr->mmap = temp.All;} +#define MARK_AUX(FIELD) {nmap_field temp; temp.All = pmr->nmap; \ + temp.Bits.FIELD = 1; pmr->nmap = temp.All;} + +#define UNMARK(FIELD) {mmap_field temp; temp.All = pmr->mmap; \ + temp.Bits.FIELD = 0; pmr->mmap = temp.All;} +#define UNMARK_AUX(FIELD) {nmap_field temp; temp.All = pmr->nmap; \ + temp.Bits.FIELD = 0; pmr->nmap = temp.All;} + +/* +WARNING!!! The following macros assume that a variable (i.e., mmap_bits + and/or nmap_bits) has been declared within the scope its' occurence + AND initialized. +*/ + +#define MARKED(FIELD) (mmap_bits.Bits.FIELD) +#define MARKED_AUX(FIELD) (nmap_bits.Bits.FIELD) + +#define UNMARK_ALL pmr->mmap = pmr->nmap = 0 + +/******************************************************************************* +Device support allows us to string several motor commands into a single +"transaction", using the calls prototyped below: + + int start_trans(dbCommon *mr) + int build_trans(int command, double *parms, dbCommon *mr) + int end_trans(struct dbCommon *mr, int go) + +For clarity and to avoid typo's, the macros defined below provide simplified +calls. + + --- NOTE WELL --- + The following macros assume that the variable "pmr" points to a motor + record, and that the variable "pdset" points to that motor record's device + support entry table: + motorRecord *pmr; + struct motor_dset *pdset = (struct motor_dset *)(pmr->dset); + + No checks are made in this code to ensure that these conditions are met. +*******************************************************************************/ +/* To begin a transaction... */ +#define INIT_MSG() (*pdset->start_trans)(pmr) + +/* To send a single command... */ +#define WRITE_MSG(cmd,parms) (*pdset->build_trans)((cmd), (parms), pmr) + +/* To end a transaction and send accumulated commands to the motor... */ +#define SEND_MSG() (*pdset->end_trans)(pmr) + + +/* +The DLY feature uses the VxWorks watchdog facility to issue a callbackRequest() +on the structure below. This structure is dynamically allocated by +init_record(). init_record() saves the pointer to this structure in the +motorRecord. See process() for use of this structure when Done Moving field +(DMOV) is TRUE. +*/ + +struct callback /* DLY feature callback structure. */ +{ + CALLBACK callback; + struct motorRecord *precord; + WDOG_ID wd_id; +}; + +STATIC void callbackFunc(struct callback * pcb) +{ + motorRecord *pmr = pcb->precord; + + /* + * It's possible user has requested stop, or in some other way rescinded + * the delay request that resulted in this callback. Check to make sure + * this callback hasn't been orphaned by events occurring between the time + * the watchdog was started and the time this function was invoked. + */ + if (pmr->mip & MIP_DELAY_REQ) + { + pmr->mip |= MIP_DELAY_ACK; + scanOnce(pmr); + } +} + + +/****************************************************************************** + enforceMinRetryDeadband() + +Calculate minumum retry deadband (.rdbd) achievable under current +circumstances, and enforce this minimum value. +******************************************************************************/ +STATIC void enforceMinRetryDeadband(motorRecord * pmr) +{ + float min_rdbd; + int encoder = (pmr->msta & EA_PRESENT) && pmr->ueip; + int backlash = pmr->bdst > 0.; + + if (backlash && encoder) + min_rdbd = 0.51 * (fabs(pmr->eres) + fabs(pmr->mres)); + else if (encoder) + min_rdbd = 0.51 * MAX(fabs(pmr->eres), fabs(pmr->mres)); + else + min_rdbd = 0.51 * fabs(pmr->mres); + + if (pmr->rdbd < min_rdbd) + { + pmr->rdbd = min_rdbd; + db_post_events(pmr, &pmr->rdbd, DBE_VALUE); + } +} + + +/****************************************************************************** + init_record() + +Called twice after an EPICS database has been loaded, and then never called +again. +*******************************************************************************/ +STATIC long init_record(motorRecord * pmr, int pass) +{ + struct motor_dset *pdset; + long status; + struct callback *pcallback; /* v3.2 */ + const unsigned short monitor_mask = DBE_VALUE; + const char errmsg[] = "motor:init_record()"; + + if (pass == 0) + { + pmr->vers = VERSION; + return(OK); + } + /* Check that we have a device-support entry table. */ + pdset = (struct motor_dset *) pmr->dset; + if (pdset == NULL) + { + recGblRecordError(S_dev_noDSET, (void *) pmr, (char *) errmsg); + return (S_dev_noDSET); + } + /* Check that DSET has pointers to functions we need. */ + if ((pdset->base.number < 8) || + (pdset->update_values == NULL) || + (pdset->start_trans == NULL) || + (pdset->build_trans == NULL) || + (pdset->end_trans == NULL)) + { + recGblRecordError(S_dev_missingSup, (void *) pmr, (char *) errmsg); + return (S_dev_missingSup); + } + + /*** setup callback for readback settling time delay (v3.2) ***/ + pcallback = (struct callback *) (calloc(1, sizeof(struct callback))); + pmr->cbak = (void *) pcallback; + callbackSetCallback((void (*)(struct callbackPvt *)) callbackFunc, + &pcallback->callback); + callbackSetPriority(pmr->prio, &pcallback->callback); + pcallback->precord = pmr; + pcallback->wd_id = wdCreate(); + + /* + * Reconcile two different ways of specifying speed and resolution; make + * sure things are sane. + */ + check_speed_and_resolution(pmr); + + /* Call device support to initialize itself and the driver */ + if (pdset->base.init_record) + { + status = (*pdset->base.init_record) (pmr); + if (status) + { + pmr->card = -1; + return (status); + } + switch (pmr->out.type) + { + case (VME_IO): + pmr->card = pmr->out.value.vmeio.card; + break; + case (CONSTANT): + case (PV_LINK): + case (DB_LINK): + case (CA_LINK): + pmr->card = -1; + break; + default: + recGblRecordError(S_db_badField, (void *) pmr, (char *) errmsg); + return(ERROR); + } + } + /* + * .dol (Desired Output Location) is a struct containing either a link to + * some other field in this database, or a constant intended to initialize + * the .val field. If the latter, get that initial value and apply it. + */ + if (pmr->dol.type == CONSTANT) + { + pmr->udf = FALSE; + recGblInitConstantLink(&pmr->dol, DBF_DOUBLE, &pmr->val); + } + + /* + * Get motor position, encoder position, status, and readback-link value by + * calling process_motor_info(). + * + * v3.2 Fix so that first call to process() doesn't appear to be a callback + * from device support. (Reset ptrans->callback_changed to NO in devSup). + */ + (*pdset->update_values) (pmr); + + /* v3.2 Set .res according to whether an encoder is in use. */ + pmr->res = ((pmr->msta & EA_PRESENT) && pmr->ueip) ? pmr->eres : pmr->mres; + + process_motor_info(pmr); + enforceMinRetryDeadband(pmr); + + /* + * If we're in closed-loop mode, initializing the user- and dial-coordinate + * motor positions (.val and .dval) is someone else's job. Otherwise, + * initialize them to the readback values (.rbv and .drbv) set by our + * recent call to process_motor_info(). + */ + if (pmr->omsl != CLOSED_LOOP) + { + pmr->val = pmr->rbv; + MARK(M_VAL); + pmr->dval = pmr->drbv; + MARK(M_DVAL); + pmr->rval = pmr->rrbv; + MARK(M_RVAL); + } + + /* Reset limits in case database values are invalid. */ + set_dial_highlimit(pmr, pdset); + set_dial_lowlimit(pmr, pdset); + + /* Initialize miscellaneous control fields. */ + pmr->dmov = TRUE; + MARK(M_DMOV); + pmr->movn = FALSE; + MARK(M_MOVN); + pmr->lspg = pmr->spmg = motorSPMG_Go; + MARK(M_SPMG); + pmr->pp = TRUE; + pmr->diff = pmr->dval - pmr->drbv; + MARK(M_DIFF); + pmr->rdif = NINT(pmr->diff / pmr->mres); + MARK(M_RDIF); + pmr->lval = pmr->val; + pmr->ldvl = pmr->dval; + pmr->lrvl = pmr->rval; + pmr->lvio = 0; /* init limit-violation field */ + if ((pmr->drbv > pmr->dhlm + pmr->res) || + (pmr->drbv < pmr->dllm - pmr->res)) + { + pmr->lvio = 1; + MARK(M_LVIO); + } + if (fabs(pmr->eres) < 1.e-9) + { + pmr->eres = pmr->mres; + MARK(M_ERES); + } + + /* Make sure readback-delay field accurately conveys the time delay we */ + /* can actually implement (nearest number of ticks of watchdog timer.) */ + pmr->dly = (NINT(vxTicksPerSecond * pmr->dly)) / (float) vxTicksPerSecond; + db_post_events(pmr, &pmr->dly, monitor_mask); + + monitor(pmr); + return(OK); +} + + +/****************************************************************************** + postProcess() + +Post process a command or motion after motor has stopped. We do this for +any of several reasons: + 1) This is the first call to process() + 2) User hit a "Stop" button, and motor has stopped. + 3) User released a "Jog*" button and motor has stopped. + 4) Hom* command has completed. + 5) User hit Hom* or Jog* while motor was moving, causing a + 'stop' to be sent to the motor, and the motor has stopped. + 6) User caused a new value to be written to the motor hardware's + position register. + 7) We hit a limit switch. +LOGIC: + Clear post process command field; PP. + IF Output Mode Select field set to CLOSED_LOOP. + Make drive values agree with readback value; + VAL <- RBV + DVAL <- DRBV + RVAL <- RRBV + DIFF <- RDIF <- 0 + ENDIF + IF done with either load-position or load-encoder-ratio commands. + Clear MIP. + ELSE IF done homing. + ... + ... + ELSE IF done stopping after jog. + Clear DMOV and JOG_STOP state in MIP. + ... + ... + ELSE IF done with backlash after jog. + Clear MIP. + IF (JOGF field ON, AND, Hard High limit OFF), OR, + (JOGR field ON, AND, Hard Low limit OFF) + Set Jog request state ON. + ENDIF + ENDIF + + +******************************************************************************/ +STATIC long postProcess(motorRecord * pmr) +{ + struct motor_dset *pdset = (struct motor_dset *) (pmr->dset); + const unsigned short monitor_mask = DBE_VALUE; + + pmr->pp = 0; + + if (pmr->omsl != CLOSED_LOOP) + { + /* Make drive values agree with readback value. */ + pmr->val = pmr->rbv; + MARK(M_VAL); + pmr->dval = pmr->drbv; + MARK(M_DVAL); + pmr->rval = pmr->rrbv; + MARK(M_RVAL); + pmr->diff = 0.; + MARK(M_DIFF); + pmr->rdif = 0; + MARK(M_RDIF); + } + + if (pmr->mip & (MIP_LOAD_P | MIP_LOAD_ER)) + { + /* We sent LOAD_POS or SET_ENC_RATIO, followed by GET_INFO. */ + pmr->mip = 0; + MARK(M_MIP); + + } + else if (pmr->mip & MIP_HOME) + { + /* Home command */ + if (pmr->mip & MIP_STOP) + { + /* Stopped and Hom* button still down. Now do Hom*. */ + double vbase = pmr->vbas / fabs(pmr->res); + double hvel = 1000 * fabs(pmr->mres / pmr->eres); + double hpos = 0; + + if (hvel <= vbase) + hvel = vbase + 1; + pmr->mip &= ~MIP_STOP; + MARK(M_MIP); + pmr->dmov = FALSE; + MARK(M_DMOV); + pmr->rcnt = 0; + MARK(M_RCNT); + INIT_MSG(); + WRITE_MSG(SET_VEL_BASE, &vbase); + WRITE_MSG(SET_VELOCITY, &hvel); + WRITE_MSG((pmr->mip & MIP_HOMF) ? HOME_FOR : HOME_REV, &hpos); + WRITE_MSG(GO, NULL); + SEND_MSG(); + pmr->pp = TRUE; + } + else + { + if (pmr->mip & MIP_HOMF) + { + pmr->mip &= ~MIP_HOMF; + MARK(M_MIP); + pmr->homf = 0; + db_post_events(pmr, &pmr->homf, monitor_mask); + } + else if (pmr->mip & MIP_HOMR) + { + + pmr->mip &= ~MIP_HOMR; + MARK(M_MIP); + pmr->homr = 0; + db_post_events(pmr, &pmr->homr, monitor_mask); + } + } + } + else if (pmr->mip & MIP_JOG_STOP) + { + pmr->mip &= ~MIP_JOG_STOP; + if (fabs(pmr->bdst) < fabs(pmr->res)) + { + /* First part of jog done. Do backlash correction. */ + double bvel = pmr->bvel / fabs(pmr->res); + double bacc = bvel / pmr->bacc; + double vbase = pmr->vbas / fabs(pmr->res); + double vel = pmr->velo / fabs(pmr->res); + double acc = vel / pmr->accl; + double bpos = (pmr->dval - pmr->bdst) / pmr->res; + double currpos = pmr->dval / pmr->res; + double newpos; + + /* Use if encoder or ReadbackLink is in use. */ + int use_rel = ((pmr->msta & EA_PRESENT) && pmr->ueip) || pmr->urip; + double relpos = pmr->diff / pmr->res; + double relbpos = ((pmr->dval - pmr->bdst) - pmr->drbv) / pmr->res; + + pmr->dmov = FALSE; + MARK(M_DMOV); + pmr->mip = MIP_JOG_BL; + MARK(M_MIP); + pmr->pp = TRUE; + + INIT_MSG(); + + WRITE_MSG(SET_VEL_BASE, &vbase); + if (vel <= vbase) + vel = vbase + 1; + WRITE_MSG(SET_VELOCITY, &vel); + WRITE_MSG(SET_ACCEL, &acc); + if (use_rel) + WRITE_MSG(MOVE_REL, &relbpos); + else + WRITE_MSG(MOVE_ABS, &bpos); + WRITE_MSG(GO, NULL); + + if (bvel <= vbase) + bvel = vbase + 1; + WRITE_MSG(SET_VELOCITY, &bvel); + WRITE_MSG(SET_ACCEL, &bacc); + if (use_rel) + { + relpos = (relpos - relbpos) * pmr->frac; + WRITE_MSG(MOVE_REL, &relpos); + } + else + { + newpos = bpos + pmr->frac * (currpos - bpos); + pmr->rval = NINT(newpos); + WRITE_MSG(MOVE_ABS, &newpos); + } + WRITE_MSG(GO, NULL); + SEND_MSG(); + } + else + { + pmr->mip = 0; /* Backup distance = 0; skip backlash. */ + MARK(M_MIP); + if ((pmr->jogf && !pmr->hls) || (pmr->jogr && !pmr->lls)) + pmr->mip |= MIP_JOG_REQ; + } + } + else if (pmr->mip & MIP_JOG_BL) + { + /* Completed backlash part of jog command. */ + pmr->mip = 0; + MARK(M_MIP); + if ((pmr->jogf && !pmr->hls) || (pmr->jogr && !pmr->lls)) + pmr->mip |= MIP_JOG_REQ; + } + /* Save old values for next call. */ + pmr->lval = pmr->val; + pmr->ldvl = pmr->dval; + pmr->lrvl = pmr->rval; + pmr->mip &= ~MIP_STOP; + MARK(M_MIP); + return(OK); +} + + +/****************************************************************************** + maybeRetry() + +Compare target with actual position. If retry is indicated, set variables so +that it will happen when we return. +******************************************************************************/ +STATIC void maybeRetry(motorRecord * pmr) +{ + if ((fabs(pmr->diff) > pmr->rdbd) && !pmr->hls && !pmr->lls) + { + /* No, we're not close enough. Try again. */ + /* If max retry count is zero, retry is disabled */ + if (pmr->rtry == 0) + { + pmr->mip &= MIP_JOG_REQ;/* Clear everything, except jog request; for + jog reactivation in postProcess(). */ + MARK(M_MIP); + } + else + { + if (++(pmr->rcnt) > pmr->rtry) + { + /* Too many retries. */ + /* pmr->spmg = motorSPMG_Pause; MARK(M_SPMG); */ + pmr->mip = 0; + MARK(M_MIP); + pmr->lval = pmr->val; + pmr->ldvl = pmr->dval; + pmr->lrvl = pmr->rval; + + /* We should probably be triggering alarms here. */ + pmr->miss = 1; + MARK_AUX(M_MISS); + } + else + { + pmr->dmov = FALSE; + MARK(M_DMOV); + pmr->mip = MIP_RETRY; + MARK(M_MIP); + } + MARK(M_RCNT); + } + } + else + { + /* Yes, we're close enough to the desired value. */ + pmr->mip &= MIP_JOG_REQ;/* Clear everything, except jog request; for + jog reactivation in postProcess(). */ + MARK(M_MIP); + if (pmr->miss) + { + pmr->miss = 0; + MARK_AUX(M_MISS); + } + + /* If motion was initiated by "Move" button, pause. */ + if (pmr->spmg == motorSPMG_Move) + { + pmr->spmg = motorSPMG_Pause; + MARK(M_SPMG); + } + } +} + + +/****************************************************************************** + process() + +Called under many different circumstances for many different reasons. + +1) Someone poked our .proc field, or some other field that is marked +'process-passive' in the motorRecord.ascii file. In this case, we +determine which fields have changed since the last time we were invoked +and attempt to act accordingly. + +2) Device support will call us periodically while a motor is moving, and +once after it stops. In these cases, we infer that device support has +called us by looking at the flag it set, report the motor's state, and +fire off readback links. If the motor has stopped, we fire off forward links +as well. + +Note that this routine handles all motor records, and that several 'copies' +of this routine may execute 'simultaneously' (in the multitasking sense), as +long as they operate on different records. This much is normal for an EPICS +record, and the normal mechanism for ensuring that a record does not get +processed by more than one 'simultaneous' copy of this routine (the .pact field) +works here as well. + +However, it is normal for an EPICS record to be either 'synchronous' (runs +to completion at every invocation of process()) or 'asynchronous' (begins +processing at one invocation and forbids all further invocations except the +callback invocation from device support that completes processing). This +record is worse than asynchronous because we can't forbid invocations while +a motor is moving (else a motor could not be stopped), nor can we complete +processing until a motor stops. + +Backlash correction would complicate this picture further, since a motor +must stop before backlash correction starts and stops it again, but device +support and the Oregon Microsystems controller allow us to string two move +commands together--even with different velocities and accelerations. + +Backlash-corrected jogs (move while user holds 'jog' button down) do +complicate the picture: we can't string the jog command together with a +backlash correction because we don't know when the user is going to release +the jog button. Worst of all, it is possible for the user to give us a +'jog' command while the motor is moving. Then we have to do the following +in separate invocations of process(): + tell the motor to stop + handle motor-in-motion callbacks while the motor slows down + recognize the stopped-motor callback and begin jogging + handle motor-in-motion callbacks while the motor jogs + recognize when the user releases the jog button and tell the motor to stop + handle motor-in-motion callbacks while the motor slows down + recognize the stopped-motor callback and begin a backlash correction + handle motor-in-motion callbacks while the motor is moving + recognize the stopped-motor callback and fire off forward links +For this reason, a fair amount of code is devoted to keeping track of +where the motor is in a sequence of movements that comprise a single motion. + +LOGIC: + Initialize. + IF this record is being processed by another task (i.e., PACT != 0). + NORMAL RETURN. + ENDIF + Set Processing Active indicator field (PACT) ON. + Call device support update_values(). + IF motor status field (MSTA) was modified. + Mark MSTA as changed. + ENDIF + IF function was invoked by a callback, OR, process delay acknowledged is ON? + Set process reason indicator to CALLBACK_DATA. + Call process_motor_info(). + IF motor-in-motion indicator (MOVN) is ON. + IF [The UEIP {"Use Encoder If Present"} is set to NO], AND, + [The URIP {"Use RDBL Link If Present"} is set to NO], AND, + [Dist. to target {DIFF} > 2 x (|Backlash Dist.| + Retry Deadband)], AND, + [Previous RDIF (pre_rdif) is nonzero], AND + [The polarity of the raw dist. to target {RDIF} has changed], AND, + [Raw Velocity is nonzero], AND, + [Raw Readback Value {RRBV} has been changed], AND, + [MIP indicates this move is either (a result of a retry),OR, + (not from a Jog* or Hom*)] + Send Stop Motor command. + Set STOP indicator in MIP ON. + Mark MIP as changed. + ENDIF + ELSE + Set the Done Moving field (DMOV) TRUE and mark DMOV as changed. + IF the High or Low limit switch is TRUE. + Set the Post Process field to TRUE. + ENDIF + IF the Post Process field is TRUE. + Call postProcess(). + ENDIF + IF the Done Moving field (DMOV) is TRUE. + Initialize delay ticks. + IF process delay acknowledged is ON, OR, ticks <= 0. + Clear process delay request and ack. indicators in MIP field. + Mark MIP as changed. + Call maybeRetry(). + ELSE + Set process delay request indicator ON in MIP field. + Mark MIP as changed. + Start WatchDog? + Set the Done Moving field (DMOV) to FALSE. + Set Processing Active indicator field (PACT) OFF. + NORMAL RETURN. + ENDIF + ENDIF + ENDIF + Save previous RDIF (pre_rdif) for target direction reversal detection in + above Stop logic. + ENDIF + IF Jog indicator is ON in MIP field. + Update Limit violation (LVIO) based on Jog direction (JOGF/JOGR) and VELO. + ELSE IF Homing indicator is ON in MIP field. + Update Limit violation (LVIO) based on Home direction (HOMF/HOMR) and VELO. + ELSE + Update Limit violation (LVIO). + ENDIF + IF Limit violation (LVIO) has changed. + Mark LVIO as changed. + IF Limit violation (LVIO) is TRUE, AND, SET is OFF (i.e., Use/Set is Set). + Set STOP field ON. + Clear JOGF and JOGR fields. + ENDIF + ENDIF + IF STOP field is ON, OR, + SPMG field Stop indicator is ON, OR, + SPMG field Pause indicator is ON, OR, + function was NOT invoked by a callback, OR, + Done Moving field (DMOV) is TRUE, OR, + RETRY indicator is ON in MIP field. + Call do_work(). + ENDIF + Update Readback output link (RLNK), call dbPutLink(). + IF Done Moving field (DMOV) is TRUE. + Process the forward-scan-link record, call recGblFwdLink(). + ENDIF + Update record timestamp, call recGblGetTimeStamp(). + Process alarms, call alarm(). + Monitor changes to record fields, call monitor(). + Set Processing Active indicator field (PACT) OFF. + Exit. + +*******************************************************************************/ +STATIC long process(motorRecord * pmr) +{ + long status = OK, process_reason; + int old_lvio = pmr->lvio; + unsigned int old_msta = pmr->msta; + struct motor_dset *pdset = (struct motor_dset *) (pmr->dset); + struct callback *pcallback = (struct callback *) pmr->cbak; /* v3.2 */ + + if (pmr->pact) + return(OK); + + pmr->pact = 1; + + /*** Who called us? ***/ + /* + * Call device support to get raw motor position/status and to see whether + * this is a callback. + */ + process_reason = (*pdset->update_values) (pmr); + if (pmr->msta != old_msta) + MARK(M_MSTA); + + if ((process_reason == CALLBACK_DATA) || (pmr->mip & MIP_DELAY_ACK)) + { + /* + * This is, effectively, a callback from device support: a + * motor-in-motion update, some asynchronous acknowledgement of a + * command we sent in a previous life, or a callback thay we requested + * to delay while readback device settled. + */ + + /* + * If we were invoked by the readback-delay callback, then this is just + * a continuation of the device-support callback. + */ + process_reason = CALLBACK_DATA; + + /* + * Get position and status from motor controller. Get readback-link + * value if link exists. + */ + process_motor_info(pmr); + + /* For Soft Channel device support; skip most record processing if + * called due to readback updates. + */ + if (pmr->dmov) + goto process_exit; + + if (pmr->movn) + { + mmap_field mmap_bits; + + mmap_bits.All = pmr->mmap; /* Initialize for MARKED. */ + + /* This is a motor-in-motion update from device support. */ + /* + * Are we going in the wrong direction? (Don't be fooled by a + * backlash correction into saying "yes". 'Jog*' and 'Hom*' motions + * don't get midcourse corrections since we don't know their + * destinations.) v3.2: Don't do this check with an encoder or + * readback device. + */ + + if (!pmr->ueip && + !pmr->urip && + (fabs(pmr->diff) > 2 * (fabs(pmr->bdst) + pmr->rdbd)) && + (pmr->pdif != 0) && + ((pmr->rdif & 0x80000000) != (pmr->pdif & 0x80000000)) && + (pmr->rvel != 0) && + (MARKED(M_RRBV)) && + ((pmr->mip == MIP_RETRY) || (pmr->mip == MIP_MOVE))) + { + + /* We're going in the wrong direction. Readback problem? */ + printf("%s:tdir = %d\n", pmr->name, pmr->tdir); + INIT_MSG(); + WRITE_MSG(STOP_AXIS, NULL); + SEND_MSG(); + pmr->mip |= MIP_STOP; + MARK(M_MIP); + } + status = 0; + } + else + { + /* Motor has stopped. */ + /* Assume we're done moving until we find out otherwise. */ + pmr->dmov = TRUE; + MARK(M_DMOV); + + if (pmr->hls || pmr->lls) + pmr->pp = TRUE; + + if (pmr->pp) + { + status = postProcess(pmr); + } /* if (pmr->pp) */ + + /* Are we "close enough" to desired position? */ + if (pmr->dmov) + { + int ticks = (int) NINT(vxTicksPerSecond * pmr->dly); + + if (pmr->mip & MIP_DELAY_ACK || (ticks <= 0)) + { + pmr->mip &= ~MIP_DELAY; + MARK(M_MIP);/* done delaying */ + maybeRetry(pmr); + } + else + { + pmr->mip |= MIP_DELAY_REQ; + MARK(M_MIP); + status = wdStart(pcallback->wd_id, ticks, + (FUNCPTR) callbackRequest, (int) pcallback); + pmr->dmov = FALSE; + pmr->pact = 0; + return(OK); + } + } + } + pmr->pdif = pmr->rdif; + } /* if (process_reason == CALLBACK_DATA) */ + + /* check for soft-limit violation */ + if (pmr->mip & MIP_JOG) + pmr->lvio = (pmr->jogf && (pmr->drbv > pmr->dhlm - pmr->velo)) || + (pmr->jogr && (pmr->drbv < pmr->dllm + pmr->velo)); + else if (pmr->mip & MIP_HOME) + pmr->lvio = (pmr->homf && (pmr->drbv > pmr->dhlm - pmr->velo)) || + (pmr->homr && (pmr->drbv < pmr->dllm + pmr->velo)); + else + pmr->lvio = (pmr->drbv > pmr->dhlm + fabs(pmr->res)) || + (pmr->drbv < pmr->dllm - fabs(pmr->res)); + + if (pmr->lvio != old_lvio) + { + MARK(M_LVIO); + if (pmr->lvio && !pmr->set) + { + pmr->stop = 1; + pmr->jogf = pmr->jogr = 0; + } + } + /* Do we need to examine the record to figure out what work to perform? */ + if (pmr->stop || (pmr->spmg == motorSPMG_Stop) || + (pmr->spmg == motorSPMG_Pause) || + (process_reason != CALLBACK_DATA) || pmr->dmov || pmr->mip & MIP_RETRY) + { + status = do_work(pmr); + } + + /* Fire off readback link */ + status = dbPutLink(&(pmr->rlnk), DBR_DOUBLE, &(pmr->rbv), 1); + + if (pmr->dmov) + recGblFwdLink(pmr); /* Process the forward-scan-link record. */ + +process_exit: + /*** We're done. Report the current state of the motor. ***/ + recGblGetTimeStamp(pmr); + alarm(pmr); /* If we've violated alarm limits, yell. */ + monitor(pmr); /* If values have changed, broadcast them. */ + pmr->pact = 0; + return (status); +} + + +/****************************************************************************** + do_work() +Here, we do the real work of processing the motor record. + +The equations that transform between user and dial coordinates follow. +Note: if user and dial coordinates differ in sign, we have to reverse the +sense of the limits in going between user and dial. + +Dial to User: +userVAL = DialVAL * DIR + OFFset +userHLM = (DIR==+) ? DialHLM + OFFset : -DialLLM + OFFset +userLLM = (DIR==+) ? DialLLM + OFFset : -DialHLM + OFFset + +User to Dial: +DialVAL = (userVAL - OFFset) / DIR +DialHLM = (DIR==+) ? userHLM - OFFset : -userLLM + OFFset +DialLLM = (DIR==+) ? userLLM - OFFset : -userHLM + OFFset + +Offset: +OFFset = userVAL - DialVAL * DIR + +LOGIC: + Initialize. + + IF Stop button activated, AND, NOT processing a STOP request. + Set MIP field to indicate processing a STOP request. + Mark MIP field as changed. Set Post process command field TRUE. + Clear Jog forward and reverse request. Clear Stop request. + Send STOP_AXIS message to controller. + NORMAL RETURN. + ENDIF + + IF Stop/Pause/Move/Go field has changed. + Update Last Stop/Pause/Move/Go field. + IF SPMG field set to STOP, OR, PAUSE. + Set MIP field to indicate processing a STOP request. + Mark MIP field changed. + Send STOP_AXIS message to controller. + IF SPMG field set to STOP. + IF Motor is moving (MOVN). + Set Post process command TRUE. + ELSE + Set VAL <- RBV and mark as changed. + Set DVAL <- DRBV and mark as changed. + Set RVAL <- RRBV and mark as changed. + ENDIF + ENDIF + NORMAL RETURN. + ELSE + Clear MIP and RCNT. Mark both as changed. + ENDIF + ENDIF + + IF MRES, OR, ERES, OR, UEIP are marked as changed. + IF UEIP set to YES, AND, MSTA indicates an encoder is present. + IF |MRES| and/or |ERES| is very near zero. + Set MRES and/or ERES to one (1.0). + ENDIF + Set sign of ERES to same sign as MRES. + ..... + ..... + ELSE + Set the [encoder (ticks) / motor (steps)] ratio to unity (1). + Set RES <- MRES. + ENDIF + - call enforceMinRetryDeadband(). + IF MSTA indicates an encoder is present. + Send the ticks/steps ratio motor command. + ENDIF + IF the SET position field is ON. + Set the PP field TRUE and send the update info. motor command. + ELSE + - call load_pos(). + ENDIF + NORMAL RETURN + ENDIF + + IF OMSL set to CLOSED_LOOP, AND, DOL type set to DB_LINK. + Use DOL field to get DB link - call dbGetLink(). + IF error return from dbGetLink(). + Set Undefined Link indicator (UDF) TRUE. + ERROR RETURN. + ENDIF + Set Undefined Link indicator (UDF) FALSE. + ELSE + IF No Limit violation, AND, (Homing forward/OR/reverse request, AND, + NOT processing Homing forward/OR/reverse, AND, NOT At + High/OR/Low Limit Switch) + IF (STOPPED, OR, PAUSED) + Set DMOV FALSE (Home command will be processed from + postProcess() when SPMG is set to GO). + ENDIF + ... + ... + NORMAL RETURN. + ENDIF + IF NOT currently jogging, AND, NOT (STOPPED, OR, PAUSED), AND, + No Limit violation, AND, Jog Request is ON. + IF (Forward jog, AND, DVAL > [DHLM - VELO]), OR, + (Reverse jog, AND, DVAL > [DLLM + VELO]) + Set limit violation (LVIO) ON. + NORMAL RETURN. + ENDIF + Set Jogging [forward/reverse] state ON. + ... + ... + NORMAL RETURN + ENDIF + IF Jog request is OFF, AND, jog is active. + Set post process TRUE. + Send STOP_AXIS message to controller. + ELSE IF process jog stop or backlash. + NORMAL RETURN. NOTE: Don't want "DVAL has changed..." logic to + get processed. + ENDIF + ENDIF + + IF VAL field has changed. + Mark VAL changed. + IF the SET position field is ON, AND, the FOFF field is "Variable". + .... + ELSE + Calculate DVAL based on VAL, OFF and DIR. + ENDIF + ENDIF + + Update LVIO field. + + IF LVIO field has changed. + Mark LVIO field. + ENDIF + + IF Limit violation occurred. + ENDIF + + IF Stop/Pause/Move/Go field set to STOP, OR, PAUSE. + NORMAL RETURN. + ENDIF + + IF DVAL field has changed, OR, NOT done moving. + Mark DVAL as changed. + Calculate new DIFF and RDIF fields and mark as changed. + IF the SET position field is ON. + Load new raw motor position w/out moving it - call load_pos(). + NORMAL RETURN. + ELSE + Calculate.... + IF last dial commanded position = current dial feedback position. + NORMAL RETURN. + ENDIF + .... + .... + IF motor moving indicator is OFF. + Set MIP MOVE indicator ON. + ..... + ..... + ..... + Send message to controller. + ENDIF + ENDIF + ENDIF + + NORMAL RETURN. + + +*******************************************************************************/ +STATIC long do_work(motorRecord * pmr) +{ + struct motor_dset *pdset = (struct motor_dset *) (pmr->dset); + int dir_positive = (pmr->dir == motorDIR_Pos); + int dir = dir_positive ? 1 : -1; + int set = pmr->set; + int stopped = (pmr->spmg == motorSPMG_Stop || pmr->spmg == motorSPMG_Pause); + int old_lvio = pmr->lvio; + const unsigned short monitor_mask = DBE_VALUE; + mmap_field mmap_bits; + + /*** Process Stop button. ***/ + if (pmr->stop && (pmr->mip != MIP_STOP)) + { + /* Stop motor. */ + pmr->pp = TRUE; + pmr->jogf = pmr->jogr = 0; + pmr->stop = 0; + goto stop_all; + } + + /*** Process Stop/Pause/Go_Pause/Go switch. *** + * + * STOP means make the motor stop and, when it does, make the drive + * fields (e.g., .val) agree with the readback fields (e.g., .rbv) + * so the motor stays stopped until somebody gives it a new place + * to go and sets the switch to MOVE or GO. + * + * PAUSE means stop the motor like the old steppermotorRecord stops + * a motor: At the next call to process() the motor will continue + * moving to .val. + * + * MOVE means Go to .val, but then wait for another explicit Go or + * Go_Pause before moving the motor, even if the .dval field + * changes. + * + * GO means Go, and then respond to any field whose change causes + * .dval to change as if .dval had received a dbPut(). + * (Implicit Go, as implemented in the old steppermotorRecord.) + * Note that a great many fields (.val, .rvl, .off, .twf, .homf, + * .jogf, etc.) can make .dval change. + */ + if (pmr->spmg != pmr->lspg) + { + pmr->lspg = pmr->spmg; + if (pmr->spmg == motorSPMG_Stop || + pmr->spmg == motorSPMG_Pause) + { + /* + * If STOP, make drive values agree with readback values (when the + * motor actually stops). + */ + if (pmr->spmg == motorSPMG_Stop) + { + if (pmr->movn) + pmr->pp = TRUE; /* Do when motor stops. */ + else + { + pmr->val = pmr->rbv; + MARK(M_VAL); + pmr->dval = pmr->drbv; + MARK(M_DVAL); + pmr->rval = pmr->rrbv; + MARK(M_RVAL); + } + } +stop_all: /* Cancel any operations. */ + if (pmr->mip & MIP_HOMF) + { + pmr->homf = 0; + db_post_events(pmr, &pmr->homf, monitor_mask); + } + else if (pmr->mip & MIP_HOMR) + { + pmr->homr = 0; + db_post_events(pmr, &pmr->homr, monitor_mask); + } + pmr->mip = MIP_STOP; + MARK(M_MIP); + INIT_MSG(); + WRITE_MSG(STOP_AXIS, NULL); + SEND_MSG(); + return(OK); + } + /* Test for "queued" jog request. */ + else if (pmr->spmg == motorSPMG_Go && ((pmr->jogf && !pmr->hls) || (pmr->jogr && !pmr->lls))) + pmr->mip = MIP_JOG_REQ; + else + { + pmr->mip = 0; + MARK(M_MIP); + pmr->rcnt = 0; + MARK(M_RCNT); + } + } + + /*** Handle changes in motor/encoder resolution, and in .ueip. ***/ + mmap_bits.All = pmr->mmap; /* Initialize for MARKED. */ + if (MARKED(M_MRES) || MARKED(M_ERES) || MARKED(M_UEIP)) + { + /* encoder pulses, motor pulses */ + double ep_mp[2]; + long m; + + /* Set the encoder ratio. Note this is blatantly device dependent. */ + if ((pmr->msta & EA_PRESENT) && pmr->ueip) + { + /* defend against divide by zero */ + if (fabs(pmr->mres) < 1.e-9) + { + pmr->mres = 1.; + MARK(M_MRES); + } + if (fabs(pmr->eres) < 1.e-9) + { + pmr->eres = pmr->mres; + MARK(M_ERES); + } + /* + * OMS hardware can't handle negative motor or encoder resolution + * in the SET_ENCODER_RATIO command. For now, we simply don't allow + * motor and encoder resolutions to differ in sign. + */ + if ((pmr->mres < 0.) != (pmr->eres < 0.)) + { + pmr->eres *= -1.; + MARK(M_ERES); + } + /* Calculate encoder ratio. */ + for (m = 10000000; (m > 1) && + (fabs(m / pmr->eres) > 1.e6 || fabs(m / pmr->mres) > 1.e6); m /= 10); + ep_mp[0] = fabs(m / pmr->eres); + ep_mp[1] = fabs(m / pmr->mres); + /* Select encoder resolution for use in later calculations. */ + pmr->res = pmr->eres; + } + else + { + ep_mp[0] = 1.; + ep_mp[1] = 1.; + pmr->res = pmr->mres; + } + + /* Make sure retry deadband is achievable */ + enforceMinRetryDeadband(pmr); + + if (pmr->msta & EA_PRESENT) + { + INIT_MSG(); + WRITE_MSG(SET_ENC_RATIO, ep_mp); + SEND_MSG(); + } + if (pmr->set) + { + pmr->pp = TRUE; + INIT_MSG(); + WRITE_MSG(GET_INFO, NULL); + SEND_MSG(); + } + else + load_pos(pmr); + + return(OK); + } + /*** Collect .val (User value) changes from all sources. ***/ + if (pmr->omsl == CLOSED_LOOP && pmr->dol.type == DB_LINK) + { + /** If we're in CLOSED_LOOP mode, get value from input link. **/ + long status; + + status = dbGetLink(&(pmr->dol), DBR_DOUBLE, &(pmr->val), NULL, NULL); + if (!RTN_SUCCESS(status)) + { + pmr->udf = TRUE; + return (1); + } + pmr->udf = FALSE; + /* Later, we'll act on this new value of .val. */ + } + else + { + /** Check out all the buttons and other sources of motion **/ + + /* Send motor to home switch in forward direction. */ + if (!pmr->lvio && + ((pmr->homf && !(pmr->mip & MIP_HOMF) && !pmr->hls) || + (pmr->homr && !(pmr->mip & MIP_HOMR) && !pmr->lls))) + { + if (stopped) + { + pmr->dmov = FALSE; + MARK(M_DMOV); + return(OK); + } + /* check for limit violation */ + if ((pmr->homf && (pmr->dval > pmr->dhlm - pmr->velo)) || + (pmr->homr && (pmr->dval < pmr->dllm + pmr->velo))) + { + pmr->lvio = 1; + MARK(M_LVIO); + return(OK); + } + pmr->mip = pmr->homf ? MIP_HOMF : MIP_HOMR; + MARK(M_MIP); + pmr->pp = TRUE; + if (pmr->movn) + { + pmr->mip |= MIP_STOP; + MARK(M_MIP); + INIT_MSG(); + WRITE_MSG(STOP_AXIS, NULL); + SEND_MSG(); + } + else + { + double vbase = pmr->vbas / fabs(pmr->res); + double hvel = 1000 * fabs(pmr->mres / pmr->eres); + double hpos = 0; + + INIT_MSG(); + WRITE_MSG(SET_VEL_BASE, &vbase); + if (hvel <= vbase) + hvel = vbase + 1; + WRITE_MSG(SET_VELOCITY, &hvel); + WRITE_MSG((pmr->mip & MIP_HOMF) ? HOME_FOR : HOME_REV, &hpos); + /* + * WRITE_MSG(SET_VELOCITY, &hvel); WRITE_MSG(MOVE_ABS, &hpos); + */ + WRITE_MSG(GO, NULL); + SEND_MSG(); + pmr->dmov = FALSE; + MARK(M_DMOV); + pmr->rcnt = 0; + MARK(M_RCNT); + } + return(OK); + } + /* + * Jog motor. Move continuously until we hit a software limit or a + * limit switch, or until user releases button. + */ + if (!(pmr->mip & MIP_JOG) && !stopped && !pmr->lvio && (pmr->mip & MIP_JOG_REQ)) + { + /* check for limit violation */ + if ((pmr->jogf && (pmr->dval > pmr->dhlm - pmr->velo)) || + (pmr->jogr && (pmr->dval < pmr->dllm + pmr->velo))) + { + pmr->lvio = 1; + MARK(M_LVIO); + return(OK); + } + pmr->mip = pmr->jogf ? MIP_JOGF : MIP_JOGR; + MARK(M_MIP); + if (pmr->movn) + { + pmr->pp = TRUE; + pmr->mip |= MIP_STOP; + MARK(M_MIP); + INIT_MSG(); + WRITE_MSG(STOP_AXIS, NULL); + SEND_MSG(); + } + else + { + double jogv = (pmr->velo * dir) / pmr->res; + double jacc = fabs(jogv) / pmr->accl; + + pmr->dmov = FALSE; + MARK(M_DMOV); + pmr->pp = TRUE; + if (pmr->jogr) + jogv = -jogv; + INIT_MSG(); + WRITE_MSG(SET_ACCEL, &jacc); + WRITE_MSG(JOG, &jogv); + SEND_MSG(); + } + return(OK); + } + /* Stop jogging. */ + if (((pmr->mip & MIP_JOG_REQ) == 0) && + ((pmr->mip & MIP_JOGF) || (pmr->mip & MIP_JOGR))) + { + /* Stop motor. When stopped, process() will correct backlash. */ + pmr->pp = TRUE; + pmr->mip |= MIP_JOG_STOP; + pmr->mip &= ~(MIP_JOGF | MIP_JOGR); + INIT_MSG(); + WRITE_MSG(STOP_AXIS, NULL); + SEND_MSG(); + return(OK); + } + else if (pmr->mip & (MIP_JOG_STOP | MIP_JOG_BL)) + return(OK); /* Normal return if process jog stop or backlash. */ + + /* + * Tweak motor forward (reverse). Increment motor's position by a + * value stored in pmr->twv. + */ + if (pmr->twf || pmr->twr) + { + pmr->val += pmr->twv * (pmr->twf ? 1 : -1); + /* Later, we'll act on this. */ + if (pmr->twf) + pmr->twf = 0; + if (pmr->twr) + pmr->twr = 0; + } + /* + * New relative value. Someone has poked a value into the "move + * relative" field (just like the .val field, but relative instead of + * absolute.) + */ + if (pmr->rlv != pmr->lrlv) + { + pmr->val += pmr->rlv; + /* Later, we'll act on this. */ + pmr->rlv = 0.; + MARK(M_RLV); + pmr->lrlv = pmr->rlv; + } + /* New raw value. Propagate to .dval and act later. */ + if (pmr->rval != pmr->lrvl) + pmr->dval = pmr->rval * pmr->res; /* Later, we'll act on this. */ + } + + /*** Collect .dval (Dial value) changes from all sources. *** + * Now we either act directly on the .val change and return, or we + * propagate it into a .dval change. + */ + if (pmr->val != pmr->lval) + { + MARK(M_VAL); + if (set && !pmr->foff) + { + /* + * Act directly on .val. and return. User wants to redefine .val + * without moving the motor and without making a change to .dval. + * Adjust the offset and recalc user limits back into agreement + * with dial limits. + */ + pmr->off = pmr->val - pmr->dval * dir; + pmr->rbv = pmr->drbv * dir + pmr->off; + MARK(M_OFF); + MARK(M_RBV); + + set_userlimits(pmr); /* Translate dial limits to user limits. */ + + pmr->lval = pmr->val; + pmr->mip = 0; + MARK(M_MIP); + pmr->dmov = TRUE; + MARK(M_DMOV); + return(OK); + } + else + /* + * User wants to move the motor, or to recalibrate both user and + * dial. Propagate .val to .dval. + */ + pmr->dval = (pmr->val - pmr->off) / dir; /* Later we'll act on this. */ + } + /* Record limit violation */ + pmr->lvio = (pmr->dval > pmr->dhlm) || (pmr->dval > pmr->dhlm + pmr->bdst) + || (pmr->dval < pmr->dllm) || (pmr->dval < pmr->dllm + pmr->bdst); + if (pmr->lvio != old_lvio) + MARK(M_LVIO); + if (pmr->lvio) + { + pmr->val = pmr->lval; + MARK(M_VAL); + pmr->dval = pmr->ldvl; + MARK(M_DVAL); + pmr->rval = pmr->lrvl; + MARK(M_RVAL); + pmr->dmov = TRUE; + MARK(M_DMOV); + return(OK); + } + + if (pmr->spmg == motorSPMG_Stop || pmr->spmg == motorSPMG_Pause) + return(OK); + + /* IF DVAL field has changed, OR, NOT done moving. */ + if (pmr->dval != pmr->ldvl || !pmr->dmov) + { + if (pmr->dval != pmr->ldvl) + MARK(M_DVAL); + pmr->diff = pmr->dval - pmr->drbv; + MARK(M_DIFF); + pmr->rdif = NINT(pmr->diff / pmr->mres); + MARK(M_RDIF); + if (set) + { + load_pos(pmr); + /* + * device support will call us back when load is done. + */ + return(OK); + } + else + { + /** Calc new raw position, and do a (backlash-corrected?) move. **/ + double rbvpos = pmr->drbv / pmr->res; /* where motor is */ + double currpos = pmr->ldvl / pmr->res; /* where we are */ + double newpos = pmr->dval / pmr->res; /* where to go */ + double vbase = pmr->vbas / fabs(pmr->res); /* base speed */ + double vel = pmr->velo / fabs(pmr->res); /* normal speed */ + double acc = vel / pmr->accl; /* normal accel. */ + /* + * 'bpos' is one backlash distance away from 'newpos'. + */ + double bpos = (pmr->dval - pmr->bdst) / pmr->res; + double bvel = pmr->bvel / fabs(pmr->res); /* backlash speed */ + double bacc = bvel / pmr->bacc; /* backlash accel. */ + double slop = 0.95 * pmr->rdbd; + /*** Use if encoder or ReadbackLink is in use. ***/ + int use_rel = ((pmr->msta & EA_PRESENT) && pmr->ueip) || pmr->urip; + double dMdR = pmr->mres / pmr->res; + double relpos = pmr->diff / pmr->res; + double relbpos = ((pmr->dval - pmr->bdst) - pmr->drbv) / pmr->res; + long rpos, npos; + /* + * Relative-move target positions with motor-resolution + * granularity. The hardware is going to convert encoder steps to + * motor steps by truncating any fractional part, instead of + * converting to nearest integer, so we prepare for that. + */ + double mRelPos = (NINT(relpos / dMdR) + ((relpos > 0) ? .5 : -.5)) * dMdR; + double mRelBPos = (NINT(relbpos / dMdR) + ((relbpos > 0) ? .5 : -.5)) * dMdR; + + rpos = NINT(rbvpos); + npos = NINT(newpos); + if (npos == rpos) + { + if (pmr->dmov == FALSE && pmr->mip == 0) + { + pmr->dmov = TRUE; + MARK(M_DMOV); + } + return(OK); + } + + /* + * Post new values, recalc .val to reflect the change in .dval. (We + * no longer know the origin of the .dval change. If user changed + * .val, we're ok as we are, but if .dval was changed directly, we + * must make .val agree.) + */ + pmr->val = pmr->dval * dir + pmr->off; + pmr->rval = NINT(pmr->dval / pmr->res); + MARK(M_DVAL); + MARK(M_VAL); + MARK(M_RVAL); + /* reset retry counter if this is not a retry */ + if ((pmr->mip & MIP_RETRY) == 0) + { + pmr->rcnt = 0; + MARK(M_RCNT); + } + + /* + * If we're within retry deadband, move only in preferred dir. + */ + if (use_rel) + { + if ((fabs(pmr->diff) < slop) && + ((mRelPos > 0) != (pmr->bdst > 0))) + { + pmr->dmov = TRUE; + MARK(M_DMOV); + return(OK); + } + } + else + { + if ((fabs(pmr->diff) < slop) && + ((newpos > currpos) != (pmr->bdst > 0))) + { + pmr->dmov = TRUE; + MARK(M_DMOV); + return(OK); + } + } + + if (pmr->movn == 0) + { + pmr->mip = MIP_MOVE; + MARK(M_MIP); + /* v1.96 Don't post dmov if special already did. */ + if (pmr->dmov) + { + pmr->dmov = FALSE; + MARK(M_DMOV); + } + pmr->ldvl = pmr->dval; + pmr->lval = pmr->val; + pmr->lrvl = pmr->rval; + + INIT_MSG(); + + /* Is backlash correction disabled? */ + if (fabs(pmr->bdst) < fabs(pmr->res)) + { + /* Yes, just move to newpos at (vel,acc) */ + WRITE_MSG(SET_VEL_BASE, &vbase); + WRITE_MSG(SET_VELOCITY, &vel); + WRITE_MSG(SET_ACCEL, &acc); + if (use_rel) + { + mRelPos *= pmr->frac; + WRITE_MSG(MOVE_REL, &relpos); + } + else + { + newpos = currpos + pmr->frac * (newpos - currpos); + WRITE_MSG(MOVE_ABS, &newpos); + } + WRITE_MSG(GO, NULL); + } + /* + * If current position is already within backlash range, or if + * we already within retry deadband of desired position, then + * we don't have to take out backlash. + */ + else if ((fabs(pmr->diff) < slop) || + (use_rel && ((relbpos < 0) == (relpos > 0))) || + (!use_rel && (((currpos + slop) > bpos) == (newpos > currpos)))) + { + /** + * Yes, assume backlash has already been taken out. + * Move to newpos at (bvel, bacc). + */ + WRITE_MSG(SET_VEL_BASE, &vbase); + WRITE_MSG(SET_VELOCITY, &bvel); + WRITE_MSG(SET_ACCEL, &bacc); + /******************************************************** + * Backlash correction imposes a much larger penalty on + * overshoot than on undershoot. Here, we allow user to + * specify (by .frac) the fraction of the backlash distance + * to move as a first approximation. When the motor stops + * and we're not yet at 'newpos', the callback will give + * us another chance, and we'll go .frac of the remaining + * distance, and so on. This algorithm is essential when + * the drive creeps after a move (e.g., piezo inchworm), + * and helpful when the readback device has a latency + * problem (e.g., interpolated encoder), or is a little + * nonlinear. (Blatantly nonlinear readback is not handled + * by the motor record.) + */ + if (use_rel) + { + mRelPos *= pmr->frac; + WRITE_MSG(MOVE_REL, &relpos); + } + else + { + newpos = currpos + pmr->frac * (newpos - currpos); + WRITE_MSG(MOVE_ABS, &newpos); + } + WRITE_MSG(GO, NULL); + } + else + { + /* We need to take out backlash. Go to bpos at (vel,acc). */ + WRITE_MSG(SET_VEL_BASE, &vbase); + WRITE_MSG(SET_VELOCITY, &vel); + WRITE_MSG(SET_ACCEL, &acc); + if (use_rel) + WRITE_MSG(MOVE_REL, &mRelBPos); + else + WRITE_MSG(MOVE_ABS, &bpos); + WRITE_MSG(GO, NULL); + + /* Move to newpos at (bvel, bacc). */ + WRITE_MSG(SET_VELOCITY, &bvel); + WRITE_MSG(SET_ACCEL, &bacc); + /* See note regarding backlash and overshoot above. */ + if (use_rel) + { + mRelPos = (mRelPos - mRelBPos) * pmr->frac; + WRITE_MSG(MOVE_REL, &mRelPos); + } + else + { + newpos = bpos + pmr->frac * (newpos - bpos); + WRITE_MSG(MOVE_ABS, &newpos); + } + WRITE_MSG(GO, NULL); + } + SEND_MSG(); + } + } + } + return(OK); +} + + +/****************************************************************************** + special() +*******************************************************************************/ +STATIC long special(struct dbAddr * paddr, int after) +{ + motorRecord *pmr = (motorRecord *) paddr->precord; + struct motor_dset *pdset = (struct motor_dset *) (pmr->dset); + const unsigned short monitor_mask = DBE_VALUE; + int dir_positive = (pmr->dir == motorDIR_Pos); + int dir = dir_positive ? 1 : -1; + BOOLEAN changed = OFF; + int fieldIndex = dbGetFieldIndex(paddr); + double offset, tmp_raw, tmp_limit, fabs_urev; + long rtnval; + motor_cmnd command; + double temp_dbl; + float *temp_flt; + + + /* + * Someone wrote to drive field. Blink .dmov unless record is disabled. + */ + if (after == 0) + { + switch (fieldIndex) + { + case motorRecordVAL: + case motorRecordDVAL: + case motorRecordRVAL: + case motorRecordRLV: + if (pmr->disa == pmr->disv || pmr->disp) + return(OK); + pmr->dmov = FALSE; + db_post_events(pmr, &pmr->dmov, monitor_mask); + return(OK); + + case motorRecordHOMF: + case motorRecordHOMR: + if (pmr->mip & MIP_HOME) + return(ERROR); /* Prevent record processing. */ + break; + } + return(OK); + } + + fabs_urev = fabs(pmr->urev); + + switch (fieldIndex) + { + /* new vbas: make sbas agree */ + case motorRecordVBAS: + if (pmr->vbas < 0.0) + { + pmr->vbas = 0.0; + db_post_events(pmr, &pmr->vbas, monitor_mask); + } + + if ((pmr->urev != 0.0) && (pmr->sbas != (temp_dbl = pmr->vbas / fabs_urev))) + { + pmr->sbas = temp_dbl; + db_post_events(pmr, &pmr->sbas, monitor_mask); + } + break; + + /* new sbas: make vbas agree */ + case motorRecordSBAS: + if (pmr->sbas < 0.0) + { + pmr->sbas = 0.0; + db_post_events(pmr, &pmr->sbas, monitor_mask); + } + + if (pmr->vbas != (temp_dbl = fabs_urev * pmr->sbas)) + { + pmr->vbas = temp_dbl; + db_post_events(pmr, &pmr->vbas, monitor_mask); + } + break; + + /* new vmax: make smax agree */ + case motorRecordVMAX: + if (pmr->vmax < 0.0) + { + pmr->vmax = 0.0; + db_post_events(pmr, &pmr->vmax, monitor_mask); + } + + if ((pmr->urev != 0.0) && (pmr->smax != (temp_dbl = pmr->vmax / fabs_urev))) + { + pmr->smax = temp_dbl; + db_post_events(pmr, &pmr->smax, monitor_mask); + } + break; + + /* new smax: make vmax agree */ + case motorRecordSMAX: + if (pmr->smax < 0.0) + { + pmr->smax = 0.0; + db_post_events(pmr, &pmr->smax, monitor_mask); + } + + if (pmr->vmax != (temp_dbl = fabs_urev * pmr->smax)) + { + pmr->vmax = temp_dbl; + db_post_events(pmr, &pmr->vmax, monitor_mask); + } + break; + + /* new velo: make s agree */ + case motorRecordVELO: + range_check(pmr, &pmr->velo, pmr->vbas, pmr->vmax); + + if ((pmr->urev != 0.0) && (pmr->s != (temp_dbl = pmr->velo / fabs_urev))) + { + pmr->s = temp_dbl; + db_post_events(pmr, &pmr->s, monitor_mask); + } + break; + + /* new s: make velo agree */ + case motorRecordS: + range_check(pmr, &pmr->s, pmr->sbas, pmr->smax); + + if (pmr->velo != (temp_dbl = fabs_urev * pmr->s)) + { + pmr->velo = temp_dbl; + db_post_events(pmr, &pmr->velo, monitor_mask); + } + break; + + /* new bvel: make sbak agree */ + case motorRecordBVEL: + range_check(pmr, &pmr->bvel, pmr->vbas, pmr->vmax); + + if ((pmr->urev != 0.0) && (pmr->sbak != (temp_dbl = pmr->bvel / fabs_urev))) + { + pmr->sbak = temp_dbl; + db_post_events(pmr, &pmr->sbak, monitor_mask); + } + break; + + /* new sbak: make bvel agree */ + case motorRecordSBAK: + range_check(pmr, &pmr->sbak, pmr->sbas, pmr->smax); + + if (pmr->bvel != (temp_dbl = fabs_urev * pmr->sbak)) + { + pmr->bvel = temp_dbl; + db_post_events(pmr, &pmr->bvel, monitor_mask); + } + break; + + /* new accl */ + case motorRecordACCL: + if (pmr->accl <= 0.0) + { + pmr->accl = 0.1; + db_post_events(pmr, &pmr->accl, monitor_mask); + } + break; + + /* new bacc */ + case motorRecordBACC: + if (pmr->bacc <= 0.0) + { + pmr->bacc = 0.1; + db_post_events(pmr, &pmr->bacc, monitor_mask); + } + break; + + /* new rdbd */ + case motorRecordRDBD: + enforceMinRetryDeadband(pmr); + break; + + /* new dir */ + case motorRecordDIR: + if (pmr->foff) + { + pmr->val = pmr->dval * dir + pmr->off; + MARK(M_VAL); + } + else + { + pmr->off = pmr->val - pmr->dval * dir; + MARK(M_OFF); + } + pmr->rbv = pmr->drbv * dir + pmr->off; + MARK(M_RBV); + set_userlimits(pmr); /* Translate dial limits to user limits. */ + break; + + /* new offset */ + case motorRecordOFF: + pmr->val = pmr->dval * dir + pmr->off; + pmr->lval = pmr->ldvl * dir + pmr->off; + pmr->rbv = pmr->drbv * dir + pmr->off; + MARK(M_VAL); + MARK(M_RBV); + set_userlimits(pmr); /* Translate dial limits to user limits. */ + break; + + /* new user high limit */ + case motorRecordHLM: + offset = pmr->off; + if (dir_positive) + { + command = SET_HIGH_LIMIT; + tmp_limit = pmr->hlm - offset; + MARK(M_DHLM); + } + else + { + command = SET_LOW_LIMIT; + tmp_limit = -(pmr->hlm) + offset; + MARK(M_DLLM); + } + + tmp_raw = tmp_limit / pmr->res; + + INIT_MSG(); + rtnval = (*pdset->build_trans)(command, &tmp_raw, pmr); + if (rtnval != OK) + { + /* If an error occured, build_trans() has reset + * dial high or low limit to controller's value. */ + + if (dir_positive) + pmr->hlm = pmr->dhlm + offset; + else + pmr->hlm = -(pmr->dllm) + offset; + } + else + { + SEND_MSG(); + if (dir_positive) + pmr->dhlm = tmp_limit; + else + pmr->dllm = tmp_limit; + } + MARK(M_HLM); + break; + + /* new user low limit */ + case motorRecordLLM: + offset = pmr->off; + if (dir_positive) + { + command = SET_LOW_LIMIT; + tmp_limit = pmr->llm - offset; + MARK(M_DLLM); + } + else + { + command = SET_HIGH_LIMIT; + tmp_limit = -(pmr->llm) + offset; + MARK(M_DHLM); + } + + tmp_raw = tmp_limit / pmr->res; + + INIT_MSG(); + rtnval = (*pdset->build_trans)(command, &tmp_raw, pmr); + if (rtnval != OK) + { + /* If an error occured, build_trans() has reset + * dial high or low limit to controller's value. */ + + if (dir_positive) + pmr->llm = pmr->dllm + offset; + else + pmr->llm = -(pmr->dhlm) + offset; + } + else + { + SEND_MSG(); + if (dir_positive) + pmr->dllm = tmp_limit; + else + pmr->dhlm = tmp_limit; + } + MARK(M_LLM); + break; + + /* new dial high limit */ + case motorRecordDHLM: + set_dial_highlimit(pmr, pdset); + break; + + /* new dial low limit */ + case motorRecordDLLM: + set_dial_lowlimit(pmr, pdset); + break; + + /* new frac (move fraction) */ + case motorRecordFRAC: + /* enforce limit */ + if (pmr->frac < 0.1) + { + pmr->frac = 0.1; + changed = ON; + } + if (pmr->frac > 1.5) + { + pmr->frac = 1.5; + changed = ON; + } + if (changed == ON) + db_post_events(pmr, &pmr->frac, monitor_mask); + break; + + /* new mres: make urev agree, and change (velo,bvel,vbas) to leave */ + /* (s,sbak,sbas) constant */ + case motorRecordMRES: + MARK(M_MRES); /* MARK it so we'll remember to tell device + * support */ + if (pmr->urev != (temp_dbl = pmr->mres * pmr->srev)) + { + pmr->urev = temp_dbl; + fabs_urev = fabs(pmr->urev); /* Update local |UREV|. */ + MARK_AUX(M_UREV); + } + goto velcheckB; + + /* new urev: make mres agree, and change (velo,bvel,vbas) to leave */ + /* (s,sbak,sbas) constant */ + + case motorRecordUREV: + if (pmr->mres != (temp_dbl = pmr->urev / pmr->srev)) + { + pmr->mres = temp_dbl; + MARK(M_MRES); + } + +velcheckB: + if (pmr->velo != (temp_dbl = fabs_urev * pmr->s)) + { + pmr->velo = temp_dbl; + MARK_AUX(M_VELO); + } + if (pmr->vbas != (temp_dbl = fabs_urev * pmr->sbas)) + { + pmr->vbas = temp_dbl; + MARK_AUX(M_VBAS); + } + if (pmr->bvel != (temp_dbl = fabs_urev * pmr->sbak)) + { + pmr->bvel = temp_dbl; + MARK_AUX(M_BVEL); + } + if (pmr->vmax != (temp_dbl = fabs_urev * pmr->smax)) + { + pmr->vmax = temp_dbl; + db_post_events(pmr, &pmr->vmax, monitor_mask); + } + break; + + /* new srev: make mres agree */ + case motorRecordSREV: + if (pmr->srev <= 0) + { + pmr->srev = 200; + MARK_AUX(M_SREV); + } + if (pmr->mres != pmr->urev / pmr->srev) + { + pmr->mres = pmr->urev / pmr->srev; + MARK(M_MRES); + } + break; + + /* new eres (encoder resolution) */ + case motorRecordERES: + MARK(M_ERES); /* MARK it so we'll remember to tell device + * support */ + break; + + /* new ueip flag */ + case motorRecordUEIP: + MARK(M_UEIP); /* MARK it so we'll remember to tell device + * support */ + /* Ideally, we should be recalculating speeds, but at the moment */ + /* we don't know whether hardware even has an encoder. */ + break; + + /* new urip flag */ + case motorRecordURIP: + break; + + /* Set to SET mode */ + case motorRecordSSET: + pmr->set = 1; + db_post_events(pmr, &pmr->set, DBE_VALUE); + break; + + /* Set to USE mode */ + case motorRecordSUSE: + pmr->set = 0; + db_post_events(pmr, &pmr->set, DBE_VALUE); + break; + + /* Set freeze-offset to freeze mode */ + case motorRecordFOF: + pmr->foff = 1; + db_post_events(pmr, &pmr->foff, DBE_VALUE); + break; + + /* Set freeze-offset to variable mode */ + case motorRecordVOF: + pmr->foff = 0; + db_post_events(pmr, &pmr->foff, DBE_VALUE); + break; + + /* + * New readback-delay time time. Show effect of roundoff to 60-Hz + * clock. + */ + case motorRecordDLY: + pmr->dly = (NINT(vxTicksPerSecond * pmr->dly)) / (float) vxTicksPerSecond; + db_post_events(pmr, &pmr->dly, monitor_mask); + break; + + /* New backlash distance. Make sure retry deadband is achievable. */ + case motorRecordBDST: + enforceMinRetryDeadband(pmr); + break; + + case motorRecordPCOF: + temp_flt = &pmr->pcof; + command = SET_PGAIN; + goto pidcof; + case motorRecordICOF: + temp_flt = &pmr->icof; + command = SET_IGAIN; + goto pidcof; + case motorRecordDCOF: + temp_flt = &pmr->dcof; + command = SET_DGAIN; +pidcof: + if ((pmr->msta & GAIN_SUPPORT) != 0) + { + if (*temp_flt < 0.0) /* Validity check; 0.0 <= gain <= 1.0 */ + { + *temp_flt = 0.0; + changed = ON; + } + else if (*temp_flt > 1.0) + { + *temp_flt = 1.0; + changed = ON; + } + + temp_dbl = *temp_flt; + + INIT_MSG(); + rtnval = (*pdset->build_trans)(command, &temp_dbl, pmr); + /* If an error occured, build_trans() has reset the gain + * parameter to a valid value for this controller. */ + if (rtnval != OK) + changed = ON; + + SEND_MSG(); + if (changed == 1) + db_post_events(pmr, temp_flt, monitor_mask); + } + break; + + case motorRecordCNEN: + if ((pmr->msta & GAIN_SUPPORT) != 0) + { + double tempdbl; + struct motor_dset *pdset = (struct motor_dset *) (pmr->dset); + + INIT_MSG(); + tempdbl = pmr->cnen; + if (pmr->cnen != 0) + WRITE_MSG(ENABLE_TORQUE, &tempdbl); + else + WRITE_MSG(DISABL_TORQUE, &tempdbl); + SEND_MSG(); + } + + case motorRecordJOGF: + if (pmr->jogf == 0) + pmr->mip &= ~MIP_JOG_REQ; + else if (pmr->mip == 0 && !pmr->hls) + pmr->mip |= MIP_JOG_REQ; + break; + + case motorRecordJOGR: + if (pmr->jogr == 0) + pmr->mip &= ~MIP_JOG_REQ; + else if (pmr->mip == 0 && !pmr->lls) + pmr->mip |= MIP_JOG_REQ; + break; + + default: + break; + } + + switch (fieldIndex) /* Re-check slew (VBAS) and backlash (VBAS) velocities. */ + { + case motorRecordVMAX: + case motorRecordSMAX: + if (pmr->vmax != 0.0 && pmr->vmax < pmr->vbas) + { + pmr->vbas = pmr->vmax; + MARK_AUX(M_VBAS); + pmr->sbas = pmr->smax; + MARK_AUX(M_SBAS); + } + goto velcheckA; + + case motorRecordVBAS: + case motorRecordSBAS: + if (pmr->vmax != 0.0 && pmr->vbas > pmr->vmax) + { + pmr->vmax = pmr->vbas; + db_post_events(pmr, &pmr->vmax, monitor_mask); + pmr->smax = pmr->sbas; + db_post_events(pmr, &pmr->smax, monitor_mask); + } +velcheckA: + range_check(pmr, &pmr->velo, pmr->vbas, pmr->vmax); + + if ((pmr->urev != 0.0) && (pmr->s != (temp_dbl = pmr->velo / fabs_urev))) + { + pmr->s = temp_dbl; + db_post_events(pmr, &pmr->s, monitor_mask); + } + + range_check(pmr, &pmr->bvel, pmr->vbas, pmr->vmax); + + if ((pmr->urev != 0.0) && (pmr->sbak != (temp_dbl = pmr->bvel / fabs_urev))) + { + pmr->sbak = temp_dbl; + db_post_events(pmr, &pmr->sbak, monitor_mask); + } + } + /* Do not process (i.e., clear) marked fields here. PP fields (e.g., MRES) must remain marked. */ + return(OK); +} + + +/****************************************************************************** + get_units() +*******************************************************************************/ +STATIC long get_units(struct dbAddr * paddr, char *units) +{ + motorRecord *pmr = (motorRecord *) paddr->precord; + int siz = dbr_units_size - 1; /* "dbr_units_size" from dbAccess.h */ + char s[30]; + int fieldIndex = dbGetFieldIndex(paddr); + + switch (fieldIndex) + { + + case motorRecordVELO: + case motorRecordBVEL: + case motorRecordVBAS: + strncpy(s, pmr->egu, DB_UNITS_SIZE); + strcat(s, "/sec"); + break; + + case motorRecordACCL: + case motorRecordBACC: + strcpy(s, "sec"); + break; + + case motorRecordS: + case motorRecordSBAS: + case motorRecordSBAK: + strcpy(s, "rev/sec"); + break; + + case motorRecordSREV: + strcpy(s, "steps/rev"); + break; + + case motorRecordUREV: + strncpy(s, pmr->egu, DB_UNITS_SIZE); + strcat(s, "/rev"); + break; + + default: + strncpy(s, pmr->egu, DB_UNITS_SIZE); + break; + } + s[siz] = '\0'; + strncpy(units, s, siz + 1); + return (0); +} + +/****************************************************************************** + get_graphic_double() +*******************************************************************************/ +STATIC long get_graphic_double(struct dbAddr * paddr, struct dbr_grDouble * pgd) +{ + motorRecord *pmr = (motorRecord *) paddr->precord; + int fieldIndex = dbGetFieldIndex(paddr); + + switch (fieldIndex) + { + + case motorRecordVAL: + case motorRecordRBV: + pgd->upper_disp_limit = pmr->hlm; + pgd->lower_disp_limit = pmr->llm; + break; + + case motorRecordDVAL: + case motorRecordDRBV: + pgd->upper_disp_limit = pmr->dhlm; + pgd->lower_disp_limit = pmr->dllm; + break; + + case motorRecordRVAL: + case motorRecordRRBV: + if (pmr->res >= 0) + { + pgd->upper_disp_limit = pmr->dhlm / pmr->res; + pgd->lower_disp_limit = pmr->dllm / pmr->res; + } + else + { + pgd->upper_disp_limit = pmr->dllm / pmr->res; + pgd->lower_disp_limit = pmr->dhlm / pmr->res; + } + break; + + default: + recGblGetGraphicDouble(paddr, pgd); + break; + } + + return (0); +} + +/****************************************************************************** + get_control_double() +*******************************************************************************/ +STATIC long + get_control_double(struct dbAddr * paddr, struct dbr_ctrlDouble * pcd) +{ + motorRecord *pmr = (motorRecord *) paddr->precord; + int fieldIndex = dbGetFieldIndex(paddr); + + switch (fieldIndex) + { + + case motorRecordVAL: + case motorRecordRBV: + pcd->upper_ctrl_limit = pmr->hlm; + pcd->lower_ctrl_limit = pmr->llm; + break; + + case motorRecordDVAL: + case motorRecordDRBV: + pcd->upper_ctrl_limit = pmr->dhlm; + pcd->lower_ctrl_limit = pmr->dllm; + break; + + case motorRecordRVAL: + case motorRecordRRBV: + if (pmr->res >= 0) + { + pcd->upper_ctrl_limit = pmr->dhlm / pmr->res; + pcd->lower_ctrl_limit = pmr->dllm / pmr->res; + } + else + { + pcd->upper_ctrl_limit = pmr->dllm / pmr->res; + pcd->lower_ctrl_limit = pmr->dhlm / pmr->res; + } + break; + + default: + recGblGetControlDouble(paddr, pcd); + break; + } + return (0); +} + +/****************************************************************************** + get_precision() +*******************************************************************************/ +STATIC long get_precision(struct dbAddr * paddr, long *precision) +{ + motorRecord *pmr = (motorRecord *) paddr->precord; + int fieldIndex = dbGetFieldIndex(paddr); + + *precision = pmr->prec; + switch (fieldIndex) + { + + case motorRecordRRBV: + case motorRecordRMP: + case motorRecordREP: + *precision = 0; + break; + + case motorRecordVERS: + *precision = 2; + break; + + default: + recGblGetPrec(paddr, precision); + break; + } + return (0); +} + + + +/****************************************************************************** + get_alarm_double() +*******************************************************************************/ +STATIC long get_alarm_double(struct dbAddr * paddr, struct dbr_alDouble * pad) +{ + motorRecord *pmr = (motorRecord *) paddr->precord; + int fieldIndex = dbGetFieldIndex(paddr); + + if (fieldIndex == motorRecordVAL || fieldIndex == motorRecordDVAL) + { + pad->upper_alarm_limit = pmr->hihi; + pad->upper_warning_limit = pmr->high; + pad->lower_warning_limit = pmr->low; + pad->lower_alarm_limit = pmr->lolo; + } + else + { + recGblGetAlarmDouble(paddr, pad); + } + return (0); +} + + +/****************************************************************************** + alarm() +*******************************************************************************/ +STATIC void alarm(motorRecord * pmr) +{ + if (pmr->udf == TRUE) + { + recGblSetSevr(pmr, UDF_ALARM, INVALID_ALARM); + return; + } + /* limit-switch and soft-limit violations */ + if (pmr->hlsv && (pmr->hls || (pmr->dval > pmr->dhlm))) + { + recGblSetSevr(pmr, HIGH_ALARM, pmr->hlsv); + return; + } + if (pmr->hlsv && (pmr->lls || (pmr->dval < pmr->dllm))) + { + recGblSetSevr(pmr, LOW_ALARM, pmr->hlsv); + return; + } + if ((pmr->msta & CNTRL_COMM_ERR) != 0) + { + pmr->msta &= ~CNTRL_COMM_ERR; + MARK(M_MSTA); + recGblSetSevr(pmr, COMM_ALARM, INVALID_ALARM); + } + return; +} + + +/****************************************************************************** + monitor() +*******************************************************************************/ +STATIC void monitor(motorRecord * pmr) +{ + unsigned short monitor_mask; + + monitor_mask = recGblResetAlarms(pmr); + monitor_mask |= (DBE_VALUE | DBE_LOG); + + /* + * Mark .val, .dval changes, and save old values for backlash correction. + */ + if (pmr->val != pmr->lval) + MARK(M_VAL); + if (pmr->dval != pmr->ldvl) + MARK(M_DVAL); + if (pmr->rval != pmr->lrvl) + MARK(M_RVAL); + + /* Catch all previous 'calls' to MARK(). */ + post_MARKed_fields(pmr, monitor_mask); + return; +} + + +/****************************************************************************** + post_MARKed_fields() +*******************************************************************************/ +STATIC void post_MARKed_fields(motorRecord * pmr, unsigned short mask) +{ + mmap_field mmap_bits; + nmap_field nmap_bits; + + mmap_bits.All = pmr->mmap; /* Initialize for MARKED. */ + nmap_bits.All = pmr->nmap; /* Initialize for MARKED_AUX. */ + + if (MARKED(M_RBV)) + { + db_post_events(pmr, &pmr->rbv, mask); + UNMARK(M_RBV); + } + if (MARKED(M_RRBV)) + { + db_post_events(pmr, &pmr->rrbv, mask); + UNMARK(M_RRBV); + } + if (MARKED(M_DRBV)) + { + db_post_events(pmr, &pmr->drbv, mask); + UNMARK(M_DRBV); + } + if (MARKED(M_RMP)) + { + db_post_events(pmr, &pmr->rmp, mask); + UNMARK(M_RMP); + } + if (MARKED(M_REP)) + { + db_post_events(pmr, &pmr->rep, mask); + UNMARK(M_REP); + } + if (MARKED(M_DIFF)) + { + db_post_events(pmr, &pmr->diff, mask); + UNMARK(M_DIFF); + } + if (MARKED(M_RDIF)) + { + db_post_events(pmr, &pmr->rdif, mask); + UNMARK(M_RDIF); + } + if (MARKED(M_MSTA)) + { + db_post_events(pmr, &pmr->msta, mask); + UNMARK(M_MSTA); + if (pmr->msta & GAIN_SUPPORT) + { + unsigned short pos_maint = (pmr->msta & EA_POSITION) ? 1 : 0; + if (pos_maint != pmr->cnen) + { + pmr->cnen = pos_maint; + db_post_events(pmr, &pmr->cnen, mask); + } + } + } + + if ((pmr->mmap == 0) && (pmr->nmap == 0)) + return; + + /* short circuit: less frequently posted PV's go below this line. */ + mmap_bits.All = pmr->mmap; /* Initialize for MARKED. */ + nmap_bits.All = pmr->nmap; /* Initialize for MARKED_AUX. */ + + if (MARKED(M_VAL)) + db_post_events(pmr, &pmr->val, mask); + if (MARKED(M_DVAL)) + db_post_events(pmr, &pmr->dval, mask); + if (MARKED(M_RVAL)) + db_post_events(pmr, &pmr->rval, mask); + if (MARKED(M_TDIR)) + db_post_events(pmr, &pmr->tdir, mask); + if (MARKED(M_MIP)) + db_post_events(pmr, &pmr->mip, mask); + if (MARKED(M_HLM)) + db_post_events(pmr, &pmr->hlm, mask); + if (MARKED(M_LLM)) + db_post_events(pmr, &pmr->llm, mask); + if (MARKED(M_SPMG)) + db_post_events(pmr, &pmr->spmg, mask); + if (MARKED(M_RCNT)) + db_post_events(pmr, &pmr->rcnt, mask); + if (MARKED(M_RLV)) + db_post_events(pmr, &pmr->rlv, mask); + if (MARKED(M_OFF)) + db_post_events(pmr, &pmr->off, mask); + if (MARKED(M_DHLM)) + db_post_events(pmr, &pmr->dhlm, mask); + if (MARKED(M_DLLM)) + db_post_events(pmr, &pmr->dllm, mask); + if (MARKED(M_HLS)) + { + db_post_events(pmr, &pmr->hls, mask); + if ((pmr->dir == motorDIR_Pos) == (pmr->res >= 0)) + db_post_events(pmr, &pmr->rhls, mask); + else + db_post_events(pmr, &pmr->rlls, mask); + } + if (MARKED(M_LLS)) + { + db_post_events(pmr, &pmr->lls, mask); + if ((pmr->dir == motorDIR_Pos) == (pmr->res >= 0)) + db_post_events(pmr, &pmr->rlls, mask); + else + db_post_events(pmr, &pmr->rhls, mask); + } + if (MARKED(M_ATHM)) + db_post_events(pmr, &pmr->athm, mask); + if (MARKED(M_MRES)) + db_post_events(pmr, &pmr->mres, mask); + if (MARKED(M_ERES)) + db_post_events(pmr, &pmr->eres, mask); + if (MARKED(M_UEIP)) + db_post_events(pmr, &pmr->ueip, mask); + if (MARKED(M_URIP)) + db_post_events(pmr, &pmr->urip, mask); + if (MARKED(M_LVIO)) + db_post_events(pmr, &pmr->lvio, mask); + if (MARKED(M_RDBD)) + db_post_events(pmr, &pmr->rdbd, mask); + + if (MARKED_AUX(M_S)) + db_post_events(pmr, &pmr->s, mask); + if (MARKED_AUX(M_SBAS)) + db_post_events(pmr, &pmr->sbas, mask); + if (MARKED_AUX(M_SBAK)) + db_post_events(pmr, &pmr->sbak, mask); + if (MARKED_AUX(M_SREV)) + db_post_events(pmr, &pmr->srev, mask); + if (MARKED_AUX(M_UREV)) + db_post_events(pmr, &pmr->urev, mask); + if (MARKED_AUX(M_VELO)) + db_post_events(pmr, &pmr->velo, mask); + if (MARKED_AUX(M_VBAS)) + db_post_events(pmr, &pmr->vbas, mask); + if (MARKED_AUX(M_BVEL)) + db_post_events(pmr, &pmr->bvel, mask); + if (MARKED_AUX(M_MISS)) + db_post_events(pmr, &pmr->miss, mask); + if (MARKED_AUX(M_ACCL)) + db_post_events(pmr, &pmr->accl, mask); + if (MARKED_AUX(M_BACC)) + db_post_events(pmr, &pmr->bacc, mask); + if (MARKED(M_MOVN)) + db_post_events(pmr, &pmr->movn, mask); + if (MARKED(M_DMOV)) + db_post_events(pmr, &pmr->dmov, mask); + + UNMARK_ALL; +} + + +/****************************************************************************** + process_motor_info() +*******************************************************************************/ +STATIC void + process_motor_info(motorRecord * pmr) +{ + unsigned long status = pmr->msta; + double old_drbv = pmr->drbv; + double old_rbv = pmr->rbv; + long old_rrbv = pmr->rrbv; + short old_tdir = pmr->tdir; + short old_movn = pmr->movn; + short old_hls = pmr->hls; + short old_lls = pmr->lls; + short old_athm = pmr->athm; + int dir = (pmr->dir == motorDIR_Pos) ? 1 : -1; + + /*** Process record fields. ***/ + + /* Calculate raw and dial readback values. */ + if ((status & EA_PRESENT) && pmr->ueip) + { + /* An encoder is present and the user wants us to use it. */ + pmr->rrbv = pmr->rep; + } + else + pmr->rrbv = pmr->rmp; + + pmr->drbv = pmr->rrbv * pmr->res; + MARK(M_RMP); + MARK(M_REP); + if (pmr->rrbv != old_rrbv) + MARK(M_RRBV); + if (pmr->drbv != old_drbv) + MARK(M_DRBV); + + /* Calculate user readback value. */ + pmr->rbv = dir * pmr->drbv + pmr->off; + if (pmr->rbv != old_rbv) + MARK(M_RBV); + + /* Get current or most recent direction. */ + pmr->tdir = (status & RA_DIRECTION) ? 1 : 0; + if (pmr->tdir != old_tdir) + MARK(M_TDIR); + + /* Get motor-now-moving indicator. */ + pmr->movn = (status & (RA_DONE | RA_PROBLEM | RA_OVERTRAVEL)) ? 0 : 1; + if (pmr->movn != old_movn) + MARK(M_MOVN); + + /* Get states of high, low limit switches. */ + pmr->rhls = (status & RA_OVERTRAVEL) && pmr->tdir; + pmr->rlls = (status & RA_OVERTRAVEL) && !pmr->tdir; + pmr->hls = ((pmr->dir == motorDIR_Pos) == (pmr->res >= 0)) ? pmr->rhls : pmr->rlls; + pmr->lls = ((pmr->dir == motorDIR_Pos) == (pmr->res >= 0)) ? pmr->rlls : pmr->rhls; + if (pmr->hls != old_hls) + MARK(M_HLS); + if (pmr->lls != old_lls) + MARK(M_LLS); + + /* Get state of motor's or encoder's home switch. */ + if ((status & EA_PRESENT) && pmr->ueip) + pmr->athm = (status & EA_HOME) ? 1 : 0; + else + pmr->athm = (status & RA_HOME) ? 1 : 0; + + if (pmr->athm != old_athm) + MARK(M_ATHM); + + + /* + * If we've got an external readback device, get Dial readback from it, and + * propagate to User readback. We do this after motor and encoder readbacks + * have been read and propagated to .rbv in case .rdbl is a link involving + * that field. + */ + if (pmr->urip) + { + long status; + + old_drbv = pmr->drbv; + status = dbGetLink(&(pmr->rdbl), DBR_DOUBLE, &(pmr->drbv), NULL, NULL); + if (!RTN_SUCCESS(status)) + pmr->drbv = old_drbv; + else + { + pmr->drbv *= pmr->rres; + pmr->rbv = pmr->drbv * dir + pmr->off; + if (pmr->drbv != old_drbv) + { + MARK(M_DRBV); + MARK(M_RBV); + } + } + } + pmr->diff = pmr->dval - pmr->drbv; + MARK(M_DIFF); + pmr->rdif = NINT(pmr->diff / pmr->res); + MARK(M_RDIF); +} + +/* Calc and load new raw position into motor w/out moving it. */ +STATIC void load_pos(motorRecord * pmr) +{ + struct motor_dset *pdset = (struct motor_dset *) (pmr->dset); + double newpos = pmr->dval / pmr->res; + + pmr->ldvl = pmr->dval; + pmr->lval = pmr->val; + pmr->lrvl = pmr->rval = (long) newpos; + + if (pmr->foff) + { + /* Translate dial value to user value. */ + if (pmr->dir == motorDIR_Pos) + pmr->val = pmr->off + pmr->dval; + else + pmr->val = pmr->off - pmr->dval; + MARK(M_VAL); + } + else + { + /* Translate dial limits to user limits. */ + if (pmr->dir == motorDIR_Pos) + pmr->off = pmr->val - pmr->dval; + else + pmr->off = pmr->val + pmr->dval; + MARK(M_OFF); + set_userlimits(pmr); /* Translate dial limits to user limits. */ + } + pmr->mip = MIP_LOAD_P; + MARK(M_MIP); + pmr->pp = TRUE; + + /* Load pos. into motor controller. Get new readback vals. */ + INIT_MSG(); + WRITE_MSG(LOAD_POS, &newpos); + SEND_MSG(); + INIT_MSG(); + WRITE_MSG(GET_INFO, NULL); + SEND_MSG(); +} + +STATIC void check_speed_and_resolution(motorRecord * pmr) +{ + const unsigned short monitor_mask = DBE_VALUE; + double temp_dbl, fabs_urev = fabs(pmr->urev); + + /* + * Reconcile two different ways of specifying speed, resolution, and make + * sure things are sane. + */ + + /* SREV (steps/revolution) must be sane. */ + if (pmr->srev <= 0) + { + pmr->srev = 200; + MARK_AUX(M_SREV); + } + + /* UREV (EGU/revolution) <--> MRES (EGU/step) */ + if (pmr->urev != 0.0) + { + pmr->mres = pmr->urev / pmr->srev; + MARK(M_MRES); + } + if (pmr->mres == 0.0) + { + pmr->mres = 1.0; + MARK(M_MRES); + } + if (pmr->urev != pmr->mres * pmr->srev) + { + pmr->urev = pmr->mres * pmr->srev; + fabs_urev = fabs(pmr->urev); /* Update local |UREV|. */ + MARK_AUX(M_UREV); + } + + /* SBAS (revolutions/sec) <--> VBAS (EGU/sec) */ + range_check(pmr, &pmr->vbas, 0.0, pmr->vmax); + + if ((pmr->urev != 0.0) && (pmr->sbas != (temp_dbl = pmr->vbas / fabs_urev))) + { + pmr->sbas = temp_dbl; + db_post_events(pmr, &pmr->sbas, monitor_mask); + } + + /* SMAX (revolutions/sec) <--> VMAX (EGU/sec) */ + if (pmr->vmax < 0.0) + { + pmr->vmax = 0.0; + db_post_events(pmr, &pmr->vmax, monitor_mask); + } + + if ((pmr->urev != 0.0) && (pmr->smax != (temp_dbl = pmr->vmax / fabs_urev))) + { + pmr->smax = temp_dbl; + db_post_events(pmr, &pmr->smax, monitor_mask); + } + + /* S (revolutions/sec) <--> VELO (EGU/sec) */ + range_check(pmr, &pmr->velo, pmr->vbas, pmr->vmax); + + if ((pmr->urev != 0.0) && (pmr->s != (temp_dbl = pmr->velo / fabs_urev))) + { + pmr->s = temp_dbl; + db_post_events(pmr, &pmr->s, monitor_mask); + } + + /* SBAK (revolutions/sec) <--> BVEL (EGU/sec) */ + range_check(pmr, &pmr->bvel, pmr->vbas, pmr->vmax); + + if ((pmr->urev != 0.0) && (pmr->sbak != (temp_dbl = pmr->bvel / fabs_urev))) + { + pmr->sbak = temp_dbl; + db_post_events(pmr, &pmr->sbak, monitor_mask); + } + + /* Sanity check on acceleration time. */ + if (pmr->accl == 0.0) + { + pmr->accl = 0.1; + MARK_AUX(M_ACCL); + } + if (pmr->bacc == 0.0) + { + pmr->bacc = 0.1; + MARK_AUX(M_BACC); + } +} + +/* +FUNCTION... void set_dial_highlimit(motorRecord *) +USAGE... Set dial-coordinate high limit. +NOTES... This function sends a command to the device to set the raw dial high +limit. This is done so that a device level function may do an error check on +the validity of the limit. This is to support those devices (e.g., MM4000) +that have their own, read-only, travel limits. +*/ +STATIC void set_dial_highlimit(motorRecord *pmr, struct motor_dset *pdset) +{ + int dir_positive = (pmr->dir == motorDIR_Pos); + double offset, tmp_raw; + long rtnval; + + tmp_raw = pmr->dhlm / pmr->res; + INIT_MSG(); + rtnval = (*pdset->build_trans)(SET_HIGH_LIMIT, &tmp_raw, pmr); + offset = pmr->off; + if (rtnval == OK) + SEND_MSG(); + + if (dir_positive) + { + pmr->hlm = pmr->dhlm + offset; + MARK(M_HLM); + } + else + { + pmr->llm = -(pmr->dhlm) + offset; + MARK(M_LLM); + } + MARK(M_DHLM); +} + +/* +FUNCTION... void set_dial_lowlimit(motorRecord *) +USAGE... Set dial-coordinate low limit. +NOTES... This function sends a command to the device to set the raw dial low +limit. This is done so that a device level function may do an error check on +the validity of the limit. This is to support those devices (e.g., MM4000) +that have their own, read-only, travel limits. +*/ +STATIC void set_dial_lowlimit(motorRecord *pmr, struct motor_dset *pdset) +{ + int dir_positive = (pmr->dir == motorDIR_Pos); + double offset, tmp_raw; + long rtnval; + + tmp_raw = pmr->dllm / pmr->res; + + INIT_MSG(); + rtnval = (*pdset->build_trans)(SET_LOW_LIMIT, &tmp_raw, pmr); + offset = pmr->off; + if (rtnval == OK) + SEND_MSG(); + + if (dir_positive) + { + pmr->llm = pmr->dllm + offset; + MARK(M_LLM); + } + else + { + pmr->hlm = -(pmr->dllm) + offset; + MARK(M_HLM); + } + MARK(M_DLLM); +} + +/* +FUNCTION... void set_userlimits(motorRecord *) +USAGE... Translate dial-coordinate limits to user-coordinate limits. +*/ +STATIC void set_userlimits(motorRecord *pmr) +{ + if (pmr->dir == motorDIR_Pos) + { + pmr->hlm = pmr->dhlm + pmr->off; + pmr->llm = pmr->dllm + pmr->off; + } + else + { + pmr->hlm = -(pmr->dllm) + pmr->off; + pmr->llm = -(pmr->dhlm) + pmr->off; + } + MARK(M_HLM); + MARK(M_LLM); +} + +/* +FUNCTION... void range_check(motorRecord *, float *, double, double) +USAGE... Limit parameter to valid range; i.e., min. <= parameter <= max. + +INPUT... parm - pointer to parameter to be range check. + min - minimum value. + max - 0 = max. range check disabled; !0 = maximum value. +*/ +STATIC void range_check(motorRecord *pmr, float *parm_ptr, double min, double max) +{ + const unsigned short monitor_mask = DBE_VALUE; + BOOLEAN changed = OFF; + double parm_val = *parm_ptr; + + if (parm_val < min) + { + parm_val = min; + changed = ON; + } + if (max != 0.0 && parm_val > max) + { + parm_val = max; + changed = ON; + } + + if (changed == ON) + { + *parm_ptr = parm_val; + db_post_events(pmr, parm_ptr, monitor_mask); + } +} + diff --git a/motorApp/MotorSrc/motorRecord.dbd b/motorApp/MotorSrc/motorRecord.dbd new file mode 100644 index 00000000..376ad112 --- /dev/null +++ b/motorApp/MotorSrc/motorRecord.dbd @@ -0,0 +1,688 @@ +# FILENAME... motorRecord.dbd + +# Version: $Revision: 1.1 $ +# Modified By: $Author: sluiter $ +# Last Modified: $Date: 2000-02-08 22:18:43 $ + +# Experimental Physics and Industrial Control System (EPICS) + +# Copyright 1991, the Regents of the University of California, +# and the University of Chicago Board of Governors. + +# This software was produced under U.S. Government contracts: +# (W-7405-ENG-36) at the Los Alamos National Laboratory, +# and (W-31-109-ENG-38) at Argonne National Laboratory. + +# Initial development by: +# The Controls and Automation Group (AT-8) +# Ground Test Accelerator +# Accelerator Technology Division +# Los Alamos National Laboratory + +# Co-developed with +# The Controls and Computing Group +# Accelerator Systems Division +# Advanced Photon Source +# Argonne National Laboratory + +#Modification Log: +#----------------- + + +menu(motorDIR) { + choice(motorDIR_Pos,"Pos") + choice(motorDIR_Neg,"Neg") +} +menu(motorSET) { + choice(motorSET_Use,"Use") + choice(motorSET_Set,"Set") +} +menu(motorMODE) { + choice(motorMODE_Position,"Position") + choice(motorMODE_Velocity,"Velocity") +} +menu(motorUEIP) { + choice(motorUEIP_No,"No") + choice(motorUEIP_Yes,"Yes") +} +menu(motorSPMG) { + choice(motorSPMG_Stop,"Stop") + choice(motorSPMG_Pause,"Pause") + choice(motorSPMG_Move,"Move") + choice(motorSPMG_Go,"Go") +} +menu(motorFOFF) { + choice(motorFOFF_Variable,"Variable") + choice(motorFOFF_Frozen,"Frozen") +} +menu(motorTORQ) { + choice(motorTORQ_Disable,"Disable") + choice(motorTORQ_Enable,"Enable") +} +recordtype(motor) { + include "dbCommon.dbd" + field(VERS,DBF_FLOAT) { + prompt("Code Version") + special(SPC_NOMOD) + initial("1") + } + field(OFF,DBF_DOUBLE) { + prompt("User Offset (EGU)") + special(SPC_MOD) + pp(TRUE) + } + field(FOFF,DBF_MENU) { + prompt("Offset-Freeze Switch") + promptgroup(GUI_COMMON) + special(SPC_MOD) + interest(1) + menu(motorFOFF) + } + field(FOF,DBF_SHORT) { + prompt("Freeze Offset") + asl(ASL0) + special(SPC_MOD) + interest(1) + } + field(VOF,DBF_SHORT) { + prompt("Variable Offset") + asl(ASL0) + special(SPC_MOD) + interest(1) + } + field(DIR,DBF_MENU) { + prompt("User Direction") + promptgroup(GUI_COMMON) + special(SPC_MOD) + pp(TRUE) + interest(1) + menu(motorDIR) + } + field(SET,DBF_MENU) { + prompt("Set/Use Switch") + interest(1) + menu(motorSET) + } + field(SSET,DBF_SHORT) { + prompt("Set SET Mode") + asl(ASL0) + special(SPC_MOD) + interest(1) + } + field(SUSE,DBF_SHORT) { + prompt("Set USE Mode") + asl(ASL0) + special(SPC_MOD) + interest(1) + } + field(VELO,DBF_FLOAT) { + prompt("Velocity (EGU/s)") + promptgroup(GUI_COMMON) + special(SPC_MOD) + interest(1) + } + field(VBAS,DBF_FLOAT) { + prompt("Base Velocity (EGU/s)") + promptgroup(GUI_COMMON) + asl(ASL0) + special(SPC_MOD) + interest(1) + } + field(VMAX,DBF_FLOAT) { + prompt("Max. Velocity (EGU/s)") + promptgroup(GUI_COMMON) + asl(ASL0) + special(SPC_MOD) + interest(1) + } + field(S,DBF_FLOAT) { + prompt("Speed (revolutions/sec)") + promptgroup(GUI_COMMON) + special(SPC_MOD) + interest(1) + } + field(SBAS,DBF_FLOAT) { + prompt("Base Speed (RPS)") + promptgroup(GUI_COMMON) + asl(ASL0) + special(SPC_MOD) + interest(1) + } + field(SMAX,DBF_FLOAT) { + prompt("Max. Speed (RPS)") + promptgroup(GUI_COMMON) + asl(ASL0) + special(SPC_MOD) + interest(1) + } + field(ACCL,DBF_FLOAT) { + prompt("Seconds to Velocity") + promptgroup(GUI_COMMON) + special(SPC_MOD) + interest(1) + initial("0.2") + } + field(BDST,DBF_FLOAT) { + prompt("BL Distance (EGU)") + promptgroup(GUI_COMMON) + special(SPC_MOD) + interest(1) + } + field(BVEL,DBF_FLOAT) { + prompt("BL Velocity (EGU/s)") + promptgroup(GUI_COMMON) + special(SPC_MOD) + interest(1) + } + field(SBAK,DBF_FLOAT) { + prompt("BL Speed (RPS)") + promptgroup(GUI_COMMON) + special(SPC_MOD) + interest(1) + } + field(BACC,DBF_FLOAT) { + prompt("BL Seconds to Velocity") + promptgroup(GUI_COMMON) + special(SPC_MOD) + interest(1) + initial("0.5") + } + field(FRAC,DBF_FLOAT) { + prompt("Move Fraction") + promptgroup(GUI_COMMON) + special(SPC_MOD) + interest(1) + initial("1") + } + field(OUT,DBF_OUTLINK) { + prompt("Output Specification") + promptgroup(GUI_COMMON) + special(SPC_MOD) + interest(1) + } + field(CARD,DBF_SHORT) { + prompt("Card Number") + special(SPC_NOMOD) + interest(3) + } + field(RDBL,DBF_INLINK) { + prompt("Readback Location") + promptgroup(GUI_COMMON) + special(SPC_MOD) + interest(1) + } + field(DOL,DBF_INLINK) { + prompt("Desired Output Loc") + promptgroup(GUI_COMMON) + special(SPC_MOD) + interest(1) + } + field(OMSL,DBF_MENU) { + prompt("Output Mode Select") + promptgroup(GUI_COMMON) + interest(1) + menu(menuOmsl) + } + field(RLNK,DBF_OUTLINK) { + prompt("Readback OutLink") + promptgroup(GUI_COMMON) + special(SPC_MOD) + interest(1) + } + field(SREV,DBF_LONG) { + prompt("Steps per Revolution") + promptgroup(GUI_COMMON) + special(SPC_MOD) + pp(TRUE) + interest(1) + initial("200") + } + field(UREV,DBF_FLOAT) { + prompt("EGU's per Revolution") + promptgroup(GUI_COMMON) + asl(ASL0) + special(SPC_MOD) + pp(TRUE) + interest(1) + } + field(MRES,DBF_FLOAT) { + prompt("Motor Step Size (EGU)") + promptgroup(GUI_COMMON) + asl(ASL0) + special(SPC_MOD) + pp(TRUE) + interest(1) + } + field(ERES,DBF_FLOAT) { + prompt("Encoder Step Size (EGU)") + promptgroup(GUI_COMMON) + special(SPC_MOD) + pp(TRUE) + interest(1) + } + field(RRES,DBF_FLOAT) { + prompt("Readback Step Size (EGU") + promptgroup(GUI_COMMON) + interest(1) + } + field(RES,DBF_FLOAT) { + prompt("Step Size (EGU)") + special(SPC_NOMOD) + interest(1) + } + field(UEIP,DBF_MENU) { + prompt("Use Encoder If Present") + promptgroup(GUI_COMMON) + special(SPC_MOD) + pp(TRUE) + interest(1) + menu(motorUEIP) + } + field(URIP,DBF_MENU) { + prompt("Use RDBL Link If Presen") + promptgroup(GUI_COMMON) + pp(TRUE) + interest(1) + menu(motorUEIP) + } + field(PREC,DBF_SHORT) { + prompt("Display Precision") + promptgroup(GUI_COMMON) + interest(1) + } + field(EGU,DBF_STRING) { + prompt("Engineering Units") + promptgroup(GUI_COMMON) + interest(1) + size(16) + } + field(HLM,DBF_FLOAT) { + prompt("User High Limit") + special(SPC_MOD) + pp(TRUE) + } + field(LLM,DBF_FLOAT) { + prompt("User Low Limit") + special(SPC_MOD) + pp(TRUE) + } + field(DHLM,DBF_FLOAT) { + prompt("Dial High Limit") + promptgroup(GUI_COMMON) + special(SPC_MOD) + pp(TRUE) + } + field(DLLM,DBF_FLOAT) { + prompt("Dial Low Limit") + promptgroup(GUI_COMMON) + special(SPC_MOD) + pp(TRUE) + } + field(HOPR,DBF_FLOAT) { + prompt("High Operating Range") + promptgroup(GUI_COMMON) + interest(1) + } + field(LOPR,DBF_FLOAT) { + prompt("Low Operating Range") + promptgroup(GUI_COMMON) + interest(1) + } + field(HLS,DBF_SHORT) { + prompt("User High Limit Switch") + special(SPC_NOMOD) + } + field(LLS,DBF_SHORT) { + prompt("User Low Limit Switch") + special(SPC_NOMOD) + } + field(RHLS,DBF_SHORT) { + prompt("Raw High Limit Switch") + special(SPC_NOMOD) + } + field(RLLS,DBF_SHORT) { + prompt("Raw Low Limit Switch") + special(SPC_NOMOD) + } + field(HIHI,DBF_FLOAT) { + prompt("Hihi Alarm Limit (EGU)") + promptgroup(GUI_COMMON) + pp(TRUE) + interest(2) + } + field(LOLO,DBF_FLOAT) { + prompt("Lolo Alarm Limit (EGU)") + promptgroup(GUI_COMMON) + pp(TRUE) + interest(2) + } + field(HIGH,DBF_FLOAT) { + prompt("High Alarm Limit (EGU)") + promptgroup(GUI_COMMON) + pp(TRUE) + interest(2) + } + field(LOW,DBF_FLOAT) { + prompt("Low Alarm Limit (EGU)") + promptgroup(GUI_COMMON) + pp(TRUE) + interest(2) + } + field(HHSV,DBF_MENU) { + prompt("Hihi Severity") + promptgroup(GUI_COMMON) + pp(TRUE) + interest(2) + menu(menuAlarmSevr) + } + field(LLSV,DBF_MENU) { + prompt("Lolo Severity") + promptgroup(GUI_COMMON) + pp(TRUE) + interest(2) + menu(menuAlarmSevr) + } + field(HSV,DBF_MENU) { + prompt("High Severity") + promptgroup(GUI_COMMON) + pp(TRUE) + interest(2) + menu(menuAlarmSevr) + } + field(LSV,DBF_MENU) { + prompt("Low Severity") + promptgroup(GUI_COMMON) + pp(TRUE) + interest(2) + menu(menuAlarmSevr) + } + field(HLSV,DBF_MENU) { + prompt("HW Limit Violation Svr") + promptgroup(GUI_COMMON) + pp(TRUE) + interest(2) + menu(menuAlarmSevr) + } + field(RDBD,DBF_FLOAT) { + prompt("Retry Deadband (EGU)") + promptgroup(GUI_COMMON) + special(SPC_MOD) + interest(1) + } + field(RCNT,DBF_SHORT) { + prompt("Retry count") + special(SPC_NOMOD) + interest(2) + } + field(RTRY,DBF_SHORT) { + prompt("Max retry count") + promptgroup(GUI_COMMON) + interest(1) + initial("10") + } + field(MISS,DBF_SHORT) { + prompt("Ran out of retries") + special(SPC_NOMOD) + interest(2) + } + field(SPMG,DBF_MENU) { + prompt("Stop/Pause/Move/Go") + pp(TRUE) + interest(1) + menu(motorSPMG) + initial("3") + } + field(LSPG,DBF_MENU) { + prompt("Last SPMG") + special(SPC_NOMOD) + interest(1) + menu(motorSPMG) + initial("3") + } + field(STOP,DBF_SHORT) { + prompt("Stop") + pp(TRUE) + interest(1) + } + field(HOMF,DBF_SHORT) { + prompt("Home Forward") + special(SPC_MOD) + pp(TRUE) + interest(1) + } + field(HOMR,DBF_SHORT) { + prompt("Home Reverse") + special(SPC_MOD) + pp(TRUE) + interest(1) + } + field(JOGF,DBF_SHORT) { + prompt("Jog motor Forward") + special(SPC_MOD) + pp(TRUE) + interest(1) + } + field(JOGR,DBF_SHORT) { + prompt("Jog motor Reverse") + special(SPC_MOD) + pp(TRUE) + interest(1) + } + field(TWF,DBF_SHORT) { + prompt("Tweak motor Forward") + pp(TRUE) + interest(1) + } + field(TWR,DBF_SHORT) { + prompt("Tweak motor Reverse") + pp(TRUE) + interest(1) + } + field(TWV,DBF_FLOAT) { + prompt("Tweak Step Size (EGU)") + promptgroup(GUI_COMMON) + interest(1) + } + field(VAL,DBF_DOUBLE) { + prompt("User Desired Value (EGU") + special(SPC_MOD) + pp(TRUE) + } + field(LVAL,DBF_DOUBLE) { + prompt("Last User Des Val (EGU)") + special(SPC_NOMOD) + interest(1) + } + field(DVAL,DBF_DOUBLE) { + prompt("Dial Desired Value (EGU") + special(SPC_MOD) + pp(TRUE) + } + field(LDVL,DBF_DOUBLE) { + prompt("Last Dial Des Val (EGU)") + special(SPC_NOMOD) + interest(1) + } + field(RVAL,DBF_LONG) { + prompt("Raw Desired Value (step") + special(SPC_MOD) + pp(TRUE) + } + field(LRVL,DBF_LONG) { + prompt("Last Raw Des Val (steps") + special(SPC_NOMOD) + interest(1) + } + field(RLV,DBF_DOUBLE) { + prompt("Relative Value (EGU)") + special(SPC_MOD) + pp(TRUE) + } + field(LRLV,DBF_DOUBLE) { + prompt("Last Rel Value (EGU)") + special(SPC_NOMOD) + interest(1) + } + field(RBV,DBF_DOUBLE) { + prompt("User Readback Value") + special(SPC_NOMOD) + } + field(DRBV,DBF_DOUBLE) { + prompt("Dial Readback Value") + special(SPC_NOMOD) + } + field(DIFF,DBF_DOUBLE) { + prompt("Difference dval-drbv") + special(SPC_NOMOD) + } + field(RDIF,DBF_LONG) { + prompt("Difference rval-rrbv") + special(SPC_NOMOD) + } + field(PDIF,DBF_LONG) { + prompt("Previous RDIF") + special(SPC_NOMOD) + } + field(RRBV,DBF_LONG) { + prompt("Raw Readback Value") + special(SPC_NOMOD) + } + field(RMP,DBF_LONG) { + prompt("Raw Motor Position") + special(SPC_NOMOD) + } + field(REP,DBF_LONG) { + prompt("Raw Encoder Position") + special(SPC_NOMOD) + } + field(RVEL,DBF_LONG) { + prompt("Raw Velocity") + promptgroup(GUI_COMMON) + special(SPC_NOMOD) + interest(1) + } + field(DMOV,DBF_SHORT) { + prompt("Done moving to value") + special(SPC_NOMOD) + initial("1") + } + field(MOVN,DBF_SHORT) { + prompt("Motor is moving") + special(SPC_NOMOD) + } + field(MSTA,DBF_ULONG) { + prompt("Motor Status") + special(SPC_NOMOD) + interest(3) + } + field(LVIO,DBF_SHORT) { + prompt("Limit violation") + special(SPC_NOMOD) + initial("1") + } + field(TDIR,DBF_SHORT) { + prompt("Direction of Travel") + special(SPC_NOMOD) + } + field(ATHM,DBF_SHORT) { + prompt("At HOME") + special(SPC_NOMOD) + } + field(PP,DBF_SHORT) { + prompt("Post process command") + special(SPC_NOMOD) + interest(2) + initial("1") + } + field(MIP,DBF_USHORT) { + prompt("Motion In Progress") + special(SPC_NOMOD) + interest(3) + } + field(MMAP,DBF_ULONG) { + prompt("Monitor Mask") + special(SPC_NOMOD) + interest(3) + } + field(NMAP,DBF_ULONG) { + prompt("Monitor Mask (more)") + special(SPC_NOMOD) + interest(3) + } + field(DLY,DBF_FLOAT) { + prompt("Readback settle time (s)") + promptgroup(GUI_COMMON) + special(SPC_MOD) + interest(1) + } + field(CBAK,DBF_NOACCESS) { + prompt("Callback structure") + special(SPC_NOMOD) + interest(4) + size(4) + extra("void *cbak") + } + field(PCOF,DBF_FLOAT) { + promptgroup(GUI_COMMON) + prompt("Proportional Gain") + special(SPC_MOD) + interest(1) + initial("0") + } + field(ICOF,DBF_FLOAT) { + promptgroup(GUI_COMMON) + prompt("Integral Gain") + special(SPC_MOD) + interest(1) + initial("0") + } + field(DCOF,DBF_FLOAT) { + promptgroup(GUI_COMMON) + prompt("Derivative Gain") + special(SPC_MOD) + interest(1) + initial("0") + } + field(CNEN,DBF_MENU) { + prompt("Enable control") + promptgroup(GUI_COMMON) + special(SPC_MOD) + menu(motorTORQ) + } + field(INIT,DBF_STRING) { + promptgroup(GUI_COMMON) + prompt("Startup commands") + size(40) + interest(1) + } + field(PREM,DBF_STRING) { + promptgroup(GUI_COMMON) + prompt("Pre-move commands") + size(40) + interest(1) + } + field(POST,DBF_STRING) { + promptgroup(GUI_COMMON) + prompt("Post-move commands") + size(40) + interest(1) + } + field(STOO,DBF_OUTLINK) { + prompt("STOP OutLink") + promptgroup(GUI_COMMON) + special(SPC_MOD) + interest(1) + } + field(DINP,DBF_INLINK) { + prompt("DMOV Input Link") + promptgroup(GUI_COMMON) + special(SPC_MOD) + interest(1) + } + field(RINP,DBF_INLINK) { + prompt("RMP Input Link") + promptgroup(GUI_COMMON) + special(SPC_MOD) + interest(1) + } +} diff --git a/motorApp/MotorSrc/motordevCom.c b/motorApp/MotorSrc/motordevCom.c new file mode 100644 index 00000000..49342785 --- /dev/null +++ b/motorApp/MotorSrc/motordevCom.c @@ -0,0 +1,521 @@ +/* +FILENAME: motordevCom.c +USAGE... This file contains device functions that are common to all motor + record device support modules. + +Version: $Revision: 1.1 $ +Modified By: $Author: sluiter $ +Last Modified: $Date: 2000-02-08 22:18:44 $ +*/ + +/* + * Original Author: Jim Kowalkowski + * Current Author: Joe Sullivan + * Date: 01/18/93 + * + * Experimental Physics and Industrial Control System (EPICS) + * + * Copyright 1991, the Regents of the University of California, + * and the University of Chicago Board of Governors. + * + * This software was produced under U.S. Government contracts: + * (W-7405-ENG-36) at the Los Alamos National Laboratory, + * and (W-31-109-ENG-38) at Argonne National Laboratory. + * + * Initial development by: + * The Controls and Automation Group (AT-8) + * Ground Test Accelerator + * Accelerator Technology Division + * Los Alamos National Laboratory + * + * Co-developed with + * The Controls and Computing Group + * Accelerator Systems Division + * Advanced Photon Source + * Argonne National Laboratory + * + * Modification Log: + * ----------------- + * .01 01-18-93 jbk initialized + * ... + * .03 03-19-96 tmm v1.10: modified encoder-ratio calculation + * .04 11-26-96 jps allow for bumpless-reboot on position + * .04a 02-19-97 tmm fixed for EPICS 3.13 + */ + + +#include +#include +#include +#include +#include +#include +#ifdef __cplusplus +extern "C" { +#include +#include +} +#else +#include +#include +#endif +#include + +#include "motorRecord.h" +#include "motor.h" +#include "motordevCom.h" +#include "motordrvCom.h" + +static void motor_callback(struct mess_node * motor_return); +static void motor_init_callback(struct mess_node * motor_return); + +/* Command set used by record support. WARNING! this must match + "motor_cmnds" in motor.h . +*/ + +long motor_init_com(int after, int brdcnt, struct driver_table *tabptr, + struct board_stat ***sptr) /* Initialize motor record device support. */ +{ + MOTOR_CARD_QUERY card_query; + int card, motor; + + if (after == 0) + { + /* allocate space for maximum possible cards in system */ + *sptr = (struct board_stat **) malloc(brdcnt * sizeof(struct board_stat *)); + + /* for each card ask driver for num of axis supported and name */ + for (card = 0; card < brdcnt; card++) + { + struct board_stat *brdptr; + + brdptr = (*sptr)[card] = (struct board_stat *) malloc(brdcnt * sizeof(struct board_stat)); + (tabptr->get_card_info) (card, &card_query, tabptr); + if (card_query.total_axis == 0) + brdptr->exists = NO; + else + { + brdptr->exists = YES; + brdptr->name = card_query.card_name; + brdptr->total_axis = card_query.total_axis; + brdptr->axis_stat = (struct axis_stat *) malloc( + card_query.total_axis * sizeof(struct axis_stat)); + + for (motor = 0; motor < card_query.total_axis; motor++) + { + brdptr->axis_stat[motor].in_use = OFF; + brdptr->axis_stat[motor].name = card_query.axis_names[motor]; + } + } + } + } + return (0); +} + + +/* +FUNCTION... motor_init_record_com - initialize a record instance. + +SYNOPSIS... motor_init_record_com( mr - motor record pointer, + brdcnt - controller board count, + tabptr - driver table pointer, + sptr - controller board status pointer) +LOGIC... + Allocate memory for motor command transaction data structure (struct motor_trans) + and point the Device Private record field to it. + Initialize the motor command transaction data structure. + ... + ... + ... + Error check "card" index, "signal" index and motor "in_use" indicator. + IF error detected. + Set the MSTA PROBLEM bit ON. + Set RES <- MRES. + Set RMP <- REP <- 0. + ERROR RETURN. + ENDIF + + Get motor information - call get_axis_info() via driver table. + Update MSTA. + Set axis assigned to a motor record indicator ON. + + Set Initialize encoder indicator based on (MSTA indicates an encoder is + present, AND, UEIP set to YES). + + IF Initialize encoder indicator is ON. + ... + ... + ELSE + Set local encoder ratio to unity. + Set RES <- MRES. + ENDIF + + Set Initialize position indicator based on (DVEL != 0, AND, RES != 0, + AND, the above "get_axis_info()" position == 0) [NOTE: non-zero controller + position takes precedence over autorestore position]. + Set Command Primitive Initialization string indicator based on (non-NULL "init" + pointer, AND, non-zero string length. + + IF (Initialize position, OR, encoder, OR, string indicators are ON) + Create initialization semaphore. + ... + ... + IF Initialize encoder indicator is ON. + Send Set Encoder Ratio command to controller. + ENDIF + IF Initialize position indicator is ON. + Send Load Position command to controller. + ENDIF + IF Command Primitive Initialization string present. + Send Initialization string to controller. + ENDIF + + Send Get Info command to controller. + ... + ... + ENDIF + + Get motor information - call get_axis_info() via driver table. + Set RMP, REP and MSTA based on updated motor information. + NORMAL RETURN. +*/ + +long motor_init_record_com(struct motorRecord *mr, int brdcnt, struct driver_table *tabptr, + struct board_stat *sptr[]) +{ + struct motor_dset *pdset = (struct motor_dset *) (mr->dset); + struct board_stat *brdptr; + int card, signal; + BOOLEAN initEncoder, initPos, initString; + struct motor_trans *ptrans; + MOTOR_AXIS_QUERY axis_query; + struct mess_node *motor_call; + double ep_mp[2]; /* encoder pulses, motor pulses */ + int rtnStat; + + /* allocate space for private field - an motor_trans structure */ + mr->dpvt = (struct motor_trans *) malloc(sizeof(struct motor_trans)); + + /* Initialize the private field. */ + ptrans = (struct motor_trans *) mr->dpvt; + ptrans->state = IDLE_STATE; + ptrans->callback_changed = NO; + ptrans->tabptr = tabptr; + + FASTLOCKINIT(&ptrans->lock); + motor_call = &(ptrans->motor_call); + + callbackSetCallback((void (*)(struct callbackPvt *)) motor_callback, + &(motor_call->callback)); + callbackSetPriority(priorityMedium, &(motor_call->callback)); + + /* check if axis already in use, then mark card, axis as in use */ + if (mr->out.type != VME_IO) /* out must be VME_IO. */ + { + recGblRecordError(S_dev_badBus, (void *) mr, + (char *) "motor_init_record_com(): Illegal OUT Bus Type"); + return(S_dev_badBus); + } + + card = mr->out.value.vmeio.card; + brdptr = sptr[card]; + signal = mr->out.value.vmeio.signal; + rtnStat = 0; + + if (card < 0 || card >= brdcnt || brdptr->exists == NO) + { + recGblRecordError(S_db_badField, (void *) mr, + (char *) "motor_init_record_com(): card does not exist!"); + rtnStat = S_db_badField; + } + else if (signal < 0 || signal >= brdptr->total_axis) + { + recGblRecordError(S_db_badField, (void *) mr, + (char *) "devOMS (init_record_com) signal does not exist!"); + rtnStat = S_db_badField; + } + else if (brdptr->axis_stat[signal].in_use == YES) + { + recGblRecordError(S_db_badField, (void *) mr, + (char *) "devOmsCom (init_record_com) motor already in use!"); + rtnStat = S_db_badField; + } + + if (rtnStat) + { + /* Initialize readback fields for simulation */ + mr->msta = RA_PROBLEM; + mr->res = mr->mres; + mr->rmp = 0; /* raw motor pulse count */ + mr->rep = 0; /* raw encoder pulse count */ + return(rtnStat); + } + + /* query motor for all info to fill into record */ + (tabptr->get_axis_info) (card, signal, &axis_query, tabptr); + mr->msta = axis_query.status; /* status info */ + brdptr->axis_stat[signal].in_use = ON; + +/*jps: setting the encoder ratio was moved from init_record() remove callbacks during iocInit */ + /* + * Set the encoder ratio. Note this is blatantly device dependent. + * Determine the number of encoder pulses and the number of motor pulses + * per engineering unit (EGU). Send an array containing this information + * to device support. + */ + initEncoder = ((mr->msta & EA_PRESENT) && mr->ueip) ? ON : OFF; + if (initEncoder == ON) + { + if (fabs(mr->mres) < 1.e-9) + mr->mres = 1.; + if (fabs(mr->eres) < 1.e-9) + mr->eres = mr->mres; + { + int m; + for (m = 10000000; (m > 1) && (fabs(m / mr->eres) > 1.e6 || + fabs(m / mr->mres) > 1.e6); m /= 10); + ep_mp[0] = m / mr->eres; /* encoder pulses per ... */ + ep_mp[1] = m / mr->mres; /* motor pulses */ + } + mr->res = mr->eres; + } + else + { + ep_mp[0] = 1.; + ep_mp[1] = 1.; + mr->res = mr->mres; + } + + initPos = (mr->dval != 0 && mr->res != 0 && axis_query.position == 0) ? ON : OFF; + /* Test for command primitive initialization string. */ + initString = (mr->init != NULL && strlen(mr->init)) ? ON : OFF; + + /* Program the device if an encoder is present */ + if (initPos == ON || initEncoder == ON || initString == ON) + { + /* Semaphore used to hold initialization until device is programmed - cleared by callback. */ + ptrans->initSem = semBCreate(SEM_Q_FIFO, SEM_EMPTY); + + /* Switch to special init callback so that record will not be processed during iocInit. */ + callbackSetCallback((void (*)(struct callbackPvt *)) motor_init_callback, + &(motor_call->callback)); + if (initEncoder == ON) + { + (*pdset->start_trans)(mr); + (*pdset->build_trans)(SET_ENC_RATIO, ep_mp, mr); + (*pdset->end_trans)(mr); + } + + if (initPos == ON) + { + double setPos = mr->dval / mr->res; + + (*pdset->start_trans)(mr); + (*pdset->build_trans)(LOAD_POS, &setPos, mr); + (*pdset->end_trans)(mr); + } + + if (initString == ON) + { + (*pdset->start_trans)(mr); + (*pdset->build_trans)(PRIMITIVE, NULL, mr); + (*pdset->end_trans)(mr); + } + + /* Changing encoder ratio may have changed the readback value. */ + (*pdset->start_trans)(mr); + (*pdset->build_trans)(GET_INFO, NULL, mr); + (*pdset->end_trans)(mr); + + /* Wait for callback w/timeout */ + if ((rtnStat = semTake(ptrans->initSem, SEM_TIMEOUT)) == ERROR) + recGblRecordError(S_dev_NoInit, (void *) mr, + (char *) "dev_NoInit (init_record_com: callback2 timeout"); + semDelete(ptrans->initSem); + + /* Restore regular record callback */ + callbackSetCallback((void (*)(struct callbackPvt *)) motor_callback, + &(motor_call->callback)); + } + + /* query motor for all info to fill into record */ + + (tabptr->get_axis_info) (card, signal, &axis_query, tabptr); + + mr->rmp = axis_query.position; /* raw motor pulse count */ + mr->rep = axis_query.encoder_position; /* raw encoder pulse count */ + mr->msta = axis_query.status; /* status info */ + return(OK); +} + +/* +FUNCTION... long motor_update_values(struct motorRecord *) +USAGE... Update the following motor record fields with the latest driver data: + RMP - Raw Motor Position. + REP - Raw Encoder Position. + RVEL - Raw Velocity. + MSTA - Motor Status. +NOTES... This function MUST BE reentrant. +*/ + +long motor_update_values(struct motorRecord * mr) +{ + struct motor_trans *ptrans; + long rc; + + rc = NOTHING_DONE; + ptrans = (struct motor_trans *) mr->dpvt; + + FASTLOCK(&ptrans->lock); + + /* raw motor pulse count */ + if (ptrans->callback_changed == YES) + { + mr->rmp = ptrans->motor_pos; + mr->rep = ptrans->encoder_pos; + mr->rvel = ptrans->vel; + mr->msta = ptrans->status; + ptrans->callback_changed = NO; + rc = CALLBACK_DATA; + } + FASTUNLOCK(&ptrans->lock); + return (rc); +} + + +/* +FUNCTION... long motor_start_trans_com(struct motorRecord *, struct board_stat **, const char *) +USAGE... Start building a transaction. +NOTES... This function MUST BE reentrant. +*/ + +long motor_start_trans_com(struct motorRecord *mr, struct board_stat **sptr, + const char *init_str) +{ + struct board_stat *brdptr; + int card = mr->out.value.vmeio.card; + int axis = mr->out.value.vmeio.signal; + struct motor_trans *trans = (struct motor_trans *) mr->dpvt; + struct mess_node *motor_call; + + if (!mr->dpvt) + return (S_dev_NoInit); + + motor_call = &(trans->motor_call); + + /* initialize area to device private field to store command that is to be + * built and mark as command build in progress. */ + + trans->state = BUILD_STATE; + motor_call->card = card; + motor_call->signal = axis; + motor_call->type = UNDEFINED; + motor_call->mrecord = (struct dbCommon *) mr; + motor_call->message[0] = (char) NULL; + motor_call->postmsgptr = (char) NULL; + motor_call->termstring = (char) NULL; + + if (init_str != NULL) + strcat(motor_call->message, init_str); + + /* Check for card/signal before building command - allow callback to + * continue if no card/signal for simulation mode. */ + brdptr = sptr[card]; + if (brdptr->exists == YES && axis < brdptr->total_axis) + motor_call->message[1] = brdptr->axis_stat[axis].name; + else + motor_call->message[1] = '*'; + return (0); +} + + +/* +FUNCTION... long motor_end_trans_com(struct motorRecord *, struct driver_table *, char *) +USAGE... Finish building a transaction. +NOTES... This function MUST BE reentrant. +*/ + +long motor_end_trans_com(struct motorRecord *mr, struct driver_table *tabptr, + char *cmnd_line_terminator) +{ + struct motor_trans *trans = (struct motor_trans *) mr->dpvt; + struct mess_node *motor_call; + long rc; + + rc = OK; + motor_call = &(trans->motor_call); + if ((*trans->tabptr->card_array)[motor_call->card] == NULL) + return(rc = ERROR); + + switch (trans->state) + { + case BUILD_STATE: + /* shut off command build in process thing */ + trans->state = IDLE_STATE; + rc = (*tabptr->send) (motor_call, tabptr, cmnd_line_terminator); + break; + + case IDLE_STATE: + default: + rc = ERROR; + } + return (rc); +} + + +/* +FUNCTION... static void motor_callback(struct mess_node * motor_return) +USAGE... Callback from driver for a motor in motion. +NOTES... This function MUST BE reentrant. +*/ + +static void motor_callback(struct mess_node * motor_return) +{ + struct motorRecord *mr = (struct motorRecord *) motor_return->mrecord; + struct rset *prset = (struct rset *) (motor_return->mrecord->rset); + struct motor_trans *ptrans; + + ptrans = (struct motor_trans *) mr->dpvt; + FASTLOCK(&ptrans->lock); + + ptrans->callback_changed = YES; + ptrans->motor_pos = motor_return->position; + ptrans->encoder_pos = motor_return->encoder_position; + ptrans->vel = motor_return->velocity; + ptrans->status = motor_return->status; + + FASTUNLOCK(&ptrans->lock); + + /* free the return data buffer */ + (ptrans->tabptr->free) (motor_return, ptrans->tabptr); + + dbScanLock((struct dbCommon *) mr); + (*prset->process) ((struct dbCommon *) mr); + dbScanUnlock((struct dbCommon *) mr); + return; +} + +/* callback from OMS driver for init_record - duplicate of + * the motor_callback without the call to process the record */ +static void motor_init_callback(struct mess_node * motor_return) +{ + struct motorRecord *mr = (struct motorRecord *) motor_return->mrecord; + struct motor_trans *ptrans; + + ptrans = (struct motor_trans *) mr->dpvt; + FASTLOCK(&ptrans->lock); + + ptrans->callback_changed = YES; + ptrans->motor_pos = motor_return->position; + ptrans->encoder_pos = motor_return->encoder_position; + ptrans->vel = motor_return->velocity; + ptrans->status = motor_return->status; + + /* free the return data buffer */ + (ptrans->tabptr->free) (motor_return, ptrans->tabptr); + semGive(ptrans->initSem); + + FASTUNLOCK(&ptrans->lock); + return; +} + diff --git a/motorApp/MotorSrc/motordevCom.h b/motorApp/MotorSrc/motordevCom.h new file mode 100644 index 00000000..bffd5adf --- /dev/null +++ b/motorApp/MotorSrc/motordevCom.h @@ -0,0 +1,86 @@ +/* +FILENAME... motordevCom.h +USAGE... This file contains definitions and structures that + are common to all motor record device support modules. + +Version: $Revision: 1.1 $ +Modified By: $Author: sluiter $ +Last Modified: $Date: 2000-02-08 22:18:44 $ +*/ + +/* + * Original Author: Ron Sluiter + * Date: 12/22/98 + * + * Experimental Physics and Industrial Control System (EPICS) + * + * Copyright 1991, the University of Chicago Board of Governors. + * + * This software was produced under U.S. Government contract + * W-31-109-ENG-38 at Argonne National Laboratory. + * + * Beamline Controls & Data Acquisition Group + * Experimental Facilities Division + * Advanced Photon Source + * Argonne National Laboratory + * + * Modification Log: + * ----------------- + */ + + +#ifndef INCmotordevCom +#define INCmotordevCom 1 + +#include "motor.h" +#include "motordrvCom.h" + +#define IDLE_STATE 0 +#define BUILD_STATE 1 +#define YES 1 +#define NO 0 + +#define SEM_TIMEOUT 50 + +/* Axis status. */ +struct axis_stat +{ + char name; + BOOLEAN in_use; /* Indicates axis assigned to a motor record. */ +}; + +/* Controller board status. */ +struct board_stat +{ + int exists; + char *name; + int total_axis; + struct axis_stat *axis_stat; +}; + +/* the device private data structure of the record */ +struct motor_trans +{ + int state; + FAST_LOCK lock; + struct mess_node motor_call; + int callback_changed; + int motor_pos; + int encoder_pos; + int vel; + unsigned long status; + SEM_ID initSem; + struct driver_table *tabptr; + BOOLEAN dpm; /* For OMS VME58 only, drive power monitoring. */ +}; + +extern long motor_update_values(struct motorRecord *); +extern long motor_init_com(int, int, struct driver_table *, + struct board_stat ***); +extern long motor_init_record_com(struct motorRecord *, int, + struct driver_table *, struct board_stat *[]); +extern long motor_start_trans_com(struct motorRecord *, struct board_stat *[], + const char *); +extern long motor_end_trans_com(struct motorRecord *, struct driver_table *, char *); + +#endif /* INCmotordevComh */ diff --git a/motorApp/MotorSrc/motordrvCom.c b/motorApp/MotorSrc/motordrvCom.c new file mode 100644 index 00000000..7e96d514 --- /dev/null +++ b/motorApp/MotorSrc/motordrvCom.c @@ -0,0 +1,544 @@ +/* +FILENAME... motordrvCom.c +USAGE... This file contains driver functions that are common + to all motor record driver modules. + +Version: $Revision: 1.1 $ +Modified By: $Author: sluiter $ +Last Modified: $Date: 2000-02-08 22:18:44 $ +*/ + +/* + * Original Author: Jim Kowalkowski + * Current Author: Joe Sullivan + * Date: 11/14/94 + * + * Experimental Physics and Industrial Control System (EPICS) + * + * Copyright 1991, the Regents of the University of California, + * and the University of Chicago Board of Governors. + * + * This software was produced under U.S. Government contracts: + * (W-7405-ENG-36) at the Los Alamos National Laboratory, + * and (W-31-109-ENG-38) at Argonne National Laboratory. + * + * Initial development by: + * The Controls and Automation Group (AT-8) + * Ground Test Accelerator + * Accelerator Technology Division + * Los Alamos National Laboratory + * + * Co-developed with + * The Controls and Computing Group + * Accelerator Systems Division + * Advanced Photon Source + * Argonne National Laboratory + * + * Modification Log: + * ----------------- + * .01 01-18-93 jbk initialized + * .02 11-14-94 jps copy drvOms.c and modify to point to vme58 driver + * ... + * .14 02-10-95 jps first released version w/interrupt supprt + * .15 02-16-95 jps add better simulation mode support + * .16 03-10-95 jps limit switch handling improvements + * .17 04-11-95 jps misc. fixes + * .18 09-27-95 jps fix GET_INFO latency bug, add axis argument to + * set_status() create add debug verbosity of 3 + * .19 05-03-96 jps convert 32bit card accesses to 16bit - vme58PCB + * version D does not support 32bit accesses. + * .20 06-20-96 jps add more security to message parameters (card #) + * .20a 02-19-97 tmm fixed for EPICS 3.13 + */ + + +#include +#include +#include +#include +#ifdef __cplusplus +extern "C" { +#include +} +#else +#include +#endif +#include +#include + +#include "motor.h" +#include "motordrvCom.h" + +/* Function declarations. */ +static int query_axis(int card, struct driver_table *tabptr, ULONG tick); +static void process_messages(struct driver_table *tabptr, ULONG tick); +static struct mess_node *get_head_node(struct driver_table *tabptr); +static struct mess_node *motor_malloc(struct circ_queue *freelistptr, FAST_LOCK *lockptr); + +char oms_trans_axis[] = {'X', 'Y', 'Z', 'T', 'U', 'V', 'R', 'S'}; + + +/* + * FUNCION... motor_task() + * LOGIC: + * WHILE FOREVER + * IF no motors in motion for this board type. + * Set "wait_time" to WAIT_FOREVER. + * ELSE + * Set "tick_curr" to time elapsed since last timeout return from semaphore pend. + * Limit "wait_time" to between 2 and "motor_scan_rate" RTOS ticks. + * IF elapsed time (i.e., "tick_curr") < user delay. + * Set "wait_time" to (user delay - elapsed time). + * IF "wait_time" < 2 OS tick. + * Set "wait_time" to 2 OS tick. + * ENDIF + * ELSE + * Set "wait_time" to to 2 OS tick. + * ENDIF + * ENDIF + * Pend on semaphore with "wait_time" timeout argument - call semTake(). + * Update elapsed time. + * IF the "any_motor_in_motion" indicator is ON. + * IF VME58 instance of this task. + * Start data area update on all cards - Call start_status(). + * ENDIF + * FOR each OMS board. + * IF motor data structure defined, AND, motor-in-motion indicator ON. + * Update OMS board status - call query_axis(). + * ENDIF + * ENDFOR + * ENDIF + * IF someone posted the semaphore. + * Process commands - call process_messages(). + * ENDIF + * ENDWHILE + * + * NOTES... This function MUST BE reentrant. + */ +/*****************************************************/ + +int motor_task(int scan_rate, int msw_tab_ptr, int lsw_tab_ptr, int a4, int a5, + int a6, int a7, int a8, int a9, int a10) +{ + struct driver_table *tabptr; + ULONG tick_used, tick_curr; + STATUS sem_ret; + int wait_time, itera; + + if (sizeof(int) >= sizeof(char *)) + tabptr = (struct driver_table *) msw_tab_ptr; + else + tabptr = (struct driver_table *) ((long) msw_tab_ptr << 16 | (long) lsw_tab_ptr); + + tick_used = tickGet(); + + for(;;) + { + if (*tabptr->any_inmotion_ptr == 0) + wait_time = WAIT_FOREVER; + else + { + tick_curr = tickGet() - tick_used; + if (tick_curr < (ULONG) scan_rate) + { + wait_time = scan_rate - tick_curr; + if (wait_time < 1) + wait_time = 1; + } + else + wait_time = 1; + } + + sem_ret = semTake(*tabptr->semptr, wait_time); + tick_used = tickGet(); + + if (*tabptr->any_inmotion_ptr) + { + if (tabptr->strtstat != NULL) + (*tabptr->strtstat) (ALL_CARDS); /* Start data area update on motor cards */ + + for (itera = 0; itera < *tabptr->cardcnt_ptr; itera++) + { + struct controller *brdptr = (*tabptr->card_array)[itera]; + if (brdptr != NULL && brdptr->motor_in_motion) + query_axis(itera, tabptr, tick_used); + } + } + if (sem_ret != ERROR) + process_messages(tabptr, tick_used); + } + return(0); +} + + +static int query_axis(int card, struct driver_table *tabptr, ULONG tick) +{ + int index; + struct controller *brdptr; + + brdptr = (*tabptr->card_array)[card]; + + for (index = 0; index < brdptr->total_axis; index++) + { + register struct mess_info *motor_info; + register struct mess_node *motor_motion; + ULONG delay; + + motor_info = &(brdptr->motor_info[index]); + if ((motor_motion = motor_info->motor_motion) != 0) + { + if (tick >= motor_info->status_delay) + delay = tick - motor_info->status_delay; + else + delay = tick + (0xFFFFFFFF - motor_info->status_delay); + } + + if (motor_motion && (delay >= 2) && (*tabptr->setstat) (card, index)) + { + struct mess_node *mess_ret; + + motor_motion->position = motor_info->position; + motor_motion->encoder_position = motor_info->encoder_position; + motor_motion->velocity = motor_info->velocity; + motor_motion->status = motor_info->status; + + mess_ret = (struct mess_node *) motor_malloc(tabptr->freeptr, tabptr->freelockptr); + mess_ret->callback = motor_motion->callback; + mess_ret->mrecord = motor_motion->mrecord; + mess_ret->position = motor_motion->position; + mess_ret->encoder_position = motor_motion->encoder_position; + mess_ret->velocity = motor_motion->velocity; + mess_ret->status = motor_motion->status; + mess_ret->type = motor_motion->type; + + if (motor_motion->status & RA_OVERTRAVEL || + motor_motion->status & RA_DONE || + motor_motion->status & RA_PROBLEM) + { + (*tabptr->query_done) (card, index, motor_motion); + brdptr->motor_in_motion--; + motor_free(motor_motion, tabptr); + motor_motion = (struct mess_node *) NULL; + motor_info->motor_motion = (struct mess_node *) NULL; + } + + callbackRequest((CALLBACK *) mess_ret); + + if (brdptr->motor_in_motion == 0) + { + SET_MM_OFF(*tabptr->any_inmotion_ptr, card); + } + } + } + return (0); +} + + +static void process_messages(struct driver_table *tabptr, ULONG tick) +{ + struct mess_node *node, *motor_motion; + ULONG delay; + + while ((node = get_head_node(tabptr))) + { + if ((node->card >= 0 && node->card < *tabptr->cardcnt_ptr) && + (*tabptr->card_array)[node->card] && + (node->signal >= 0 && node->signal < (*tabptr->card_array)[node->card]->total_axis)) + { + struct mess_info *motor_info; + + motor_info = &((*tabptr->card_array)[node->card]->motor_info[node->signal]); + motor_motion = motor_info->motor_motion; + + switch (node->type) + { + case QUERY: + (*tabptr->sendmsg) (node->card, node->message, (char) NULL); + (*tabptr->getmsg) (node->card, node->message, 1); + callbackRequest((CALLBACK *) node); + break; + + case VELOCITY: + (*tabptr->sendmsg) (node->card, node->message, (char) NULL); + + /* + * this is tricky - another motion is here there is a very + * large assumption being made here: that the person who sent + * the previous motion is the same one that is sending this + * one, if he weren't, the guy that sent the original would + * never get notified of finish motion. This makes sense in + * record processing since only one record can be assigned to + * an axis and sent commands to it. An improvement would be + * to check and see if the record pointers were the same, if + * they were not, then send a finish message to the previous + * registered motion guy. + */ + + if (!motor_motion) /* if NULL */ + (*tabptr->card_array)[node->card]->motor_in_motion++; + else + motor_free(motor_motion, tabptr); + + SET_MM_ON(*tabptr->any_inmotion_ptr, node->card); + motor_info->motor_motion = node; + motor_info->status_delay = tick; + break; + + case MOTION: + (*tabptr->sendmsg) (node->card, node->message, (char) NULL); + + /* this is tricky - see velocity comment */ + if (!motor_motion) /* if NULL */ + (*tabptr->card_array)[node->card]->motor_in_motion++; + else + motor_free(motor_motion, tabptr); + + SET_MM_ON(*tabptr->any_inmotion_ptr, node->card); + motor_info->no_motion_count = 0; + motor_info->motor_motion = node; + motor_info->status_delay = tick; + break; + + case INFO: + if (tick >= motor_info->status_delay) + delay = tick - motor_info->status_delay; + else + delay = tick + (0xFFFFFFFF - motor_info->status_delay); + if (delay < 2) /* Status update delay - needed for OMS. */ + taskDelay((int) (2 - delay)); /* 2 RTOS tick delay. */ + + if (tabptr->strtstat != NULL) + (*tabptr->strtstat) (node->card); + (*tabptr->setstat) (node->card, node->signal); + + node->position = motor_info->position; + node->encoder_position = motor_info->encoder_position; + node->status = motor_info->status; + +/*============================================================================= +* node->status & RA_DONE is not a reliable indicator of anything, in this case, +* since set_status() didn't ask the OMS controller to set the "Done" flag on +* command completion. +* Nevertheless, recMotor:process() needs to know whether the motor has stopped, +* and this we can tell by looking for a struct motor_motion. +==============================================================================*/ + if (motor_motion) + node->status &= ~RA_DONE; + else + node->status |= RA_DONE; + + callbackRequest((CALLBACK *) node); + break; + + case MOVE_TERM: + (*tabptr->sendmsg) (node->card, node->message, (char) NULL); + motor_free(node, tabptr); /* free message buffer */ + break; + + default: + (*tabptr->sendmsg) (node->card, node->message, (char) NULL); + motor_free(node, tabptr); /* free message buffer */ + motor_info->status_delay = tick; + break; + } + } + else + { + node->position = 0; + node->encoder_position = 0; + node->velocity = 0; + node->status = RA_PROBLEM; + callbackRequest((CALLBACK *) node); + } + } +} + + +/*****************************************************/ +/* Get a message off the queue */ +/* get_head_node() */ +/*****************************************************/ +static struct mess_node *get_head_node(struct driver_table *tabptr) +{ + struct circ_queue *qptr; + struct mess_node *node; + + FASTLOCK(tabptr->quelockptr); + qptr = tabptr->queptr; + node = qptr->head; + + /* delete node from list */ + if (node) + { + if (node == qptr->head) + qptr->head = node->next; + if (node == qptr->tail) + qptr->tail = NULL; + } + FASTUNLOCK(tabptr->quelockptr); + return (node); +} + +/* + * FUNCTION... motor_send() + * + * USAGE... Send a command to the motor task queue. + * + * LOGIC... + * Allocate message node - call motor_malloc(). + * Copy info. from input node to new node. + * Process node based on "type". + * Lock motor task queue. + * Insert new node at tail of queue. + */ + +int motor_send(struct mess_node *u_msg, struct driver_table *tabptr, char *cmnd_line_terminator) +{ + struct mess_node *new_message; + struct circ_queue *qptr; + + new_message = motor_malloc(tabptr->freeptr, tabptr->freelockptr); + new_message->callback = u_msg->callback; + new_message->next = (struct mess_node *) NULL; + new_message->type = u_msg->type; + new_message->signal = u_msg->signal; + new_message->card = u_msg->card; + new_message->mrecord = u_msg->mrecord; + new_message->status = 0; + strcpy(new_message->message, u_msg->message); + new_message->postmsgptr = u_msg->postmsgptr; + new_message->termstring = u_msg->termstring; + + switch (new_message->type) + { + case MOVE_TERM: + case MOTION: + if (new_message->termstring != NULL) + strcat(new_message->message, new_message->termstring); + break; + case VELOCITY: + case IMMEDIATE: + case QUERY: + case INFO: + break; + default: + return (-1); + } + + strcat(new_message->message, cmnd_line_terminator); + + FASTLOCK(tabptr->quelockptr); + qptr = tabptr->queptr; + if (qptr->tail) + { + qptr->tail->next = new_message; + qptr->tail = new_message; + } + else + { + qptr->tail = new_message; + qptr->head = new_message; + } + FASTUNLOCK(tabptr->quelockptr); + + semGive(*tabptr->semptr); + return (0); +} + +static struct mess_node *motor_malloc(struct circ_queue *freelistptr, FAST_LOCK *lockptr) +{ + struct mess_node *node; + + FASTLOCK(lockptr); + + if (!freelistptr->head) + node = (struct mess_node *) malloc(sizeof(struct mess_node)); + else + { + node = freelistptr->head; + freelistptr->head = node->next; + if (!freelistptr->head) + freelistptr->tail = (struct mess_node *) NULL; + } + FASTUNLOCK(lockptr); + return (node); +} + +int motor_free(struct mess_node * node, struct driver_table *tabptr) +{ + struct circ_queue *freelistptr; + + freelistptr = tabptr->freeptr; + FASTLOCK(tabptr->freelockptr); + + node->next = (struct mess_node *) NULL; + + if (freelistptr->tail) + { + freelistptr->tail->next = node; + freelistptr->tail = node; + } + else + { + freelistptr->head = node; + freelistptr->tail = node; + } + + FASTUNLOCK(tabptr->freelockptr); + return (0); +} + +/*---------------------------------------------------------------------*/ +/* + * both of these routines are only to be used at initialization time - before + * queuing transactions starts for an axis. These routines do no locking, + * since transactions will not enter the queue for an axis until after they + * have run. + */ + +/* return the card information to caller */ +int motor_card_info(int card, MOTOR_CARD_QUERY * cq, struct driver_table *tabptr) +{ + struct controller *brdptr; + + brdptr = (*tabptr->card_array)[card]; + if (card >= 0 && + card < *tabptr->cardcnt_ptr && + brdptr != NULL) + { + cq->total_axis = brdptr->total_axis; + cq->card_name = brdptr->ident; + cq->axis_names = oms_trans_axis; + } + else + cq->total_axis = 0; + + return (0); +} + +/* return information for an axis to the caller */ +int motor_axis_info(int card, int signal, MOTOR_AXIS_QUERY * aq, struct driver_table *tabptr) +{ + struct controller *brdptr; + + brdptr = (*tabptr->card_array)[card]; + if (card >= 0 && + card < *tabptr->cardcnt_ptr && + brdptr != NULL && + signal >= 0 && + signal < brdptr->total_axis) + { + aq->position = brdptr->motor_info[signal].position; + aq->encoder_position = + brdptr->motor_info[signal].encoder_position; + aq->status = brdptr->motor_info[signal].status; + } + else + { + aq->position = aq->encoder_position = 0; + aq->status = RA_PROBLEM; + } + + return (0); +} + diff --git a/motorApp/MotorSrc/motordrvCom.h b/motorApp/MotorSrc/motordrvCom.h new file mode 100644 index 00000000..e4350c21 --- /dev/null +++ b/motorApp/MotorSrc/motordrvCom.h @@ -0,0 +1,205 @@ +/* +FILENAME... motordrvCom.h + +USAGE... This file contains definitions and structures that + are common to all motor record driver support modules. + +Version: $Revision: 1.1 $ +Modified By: $Author: sluiter $ +Last Modified: $Date: 2000-02-08 22:18:45 $ +*/ + +/* + * Current Author: Ron Sluiter + * Date: 12/22/98 + * + * Experimental Physics and Industrial Control System (EPICS) + * + * Copyright 1991, the Regents of the University of California, + * and the University of Chicago Board of Governors. + * + * This software was produced under U.S. Government contracts: + * (W-7405-ENG-36) at the Los Alamos National Laboratory, + * and (W-31-109-ENG-38) at Argonne National Laboratory. + * + * Initial development by: + * The Controls and Automation Group (AT-8) + * Ground Test Accelerator + * Accelerator Technology Division + * Los Alamos National Laboratory + * + * Co-developed with + * The Controls and Computing Group + * Accelerator Systems Division + * Advanced Photon Source + * Argonne National Laboratory + * + * Modification Log: + * ----------------- + */ + + +#ifndef INCmotordrvComh +#define INCmotordrvComh 1 + +#include + +/* +Valid command types for the driver. The order is of importance; 0 is +lowest importance. Device support will choose the greatest one to +use as the driver transaction type. +*/ + +#define UNDEFINED (unsigned char)0 /* garbage type */ +#define IMMEDIATE (unsigned char)1 /* 'i' an execute immediate, no reply */ +#define MOVE_TERM (unsigned char)2 /* 't' terminate a previous active motion */ +#define MOTION (unsigned char)3 /* 'm' will produce motion updates */ +#define VELOCITY (unsigned char)4 /* 'v' make motion updates till MOVE_TERM */ +#define INFO (unsigned char)5 /* 'f' get curr motor/encoder pos and stat */ +#define QUERY (unsigned char)6 /* 'q' specialty type, not needed */ + +/* Macros used to set/clear bits in any_motor_in_motion variable. */ +#define SET_MM_ON(v,a) v|=(1<3kB) +# +include $(TOP)/config/RULES.Vx +#---------------------------------------- +# ADD RULES AFTER THIS LINE diff --git a/motorApp/NewportSrc/devMM3000.c b/motorApp/NewportSrc/devMM3000.c new file mode 100644 index 00000000..af5d88e7 --- /dev/null +++ b/motorApp/NewportSrc/devMM3000.c @@ -0,0 +1,362 @@ +/* +FILENAME... devMM3000.c +USAGE... Motor record device level support for Newport MM3000. + +Version: $Revision: 1.1 $ +Modified By: $Author: sluiter $ +Last Modified: $Date: 2000-02-08 22:18:51 $ +*/ + +/* + * Original Author: Mark Rivers + * Date: 10/16/97 + * Current Author: Ron Sluiter + * + * Experimental Physics and Industrial Control System (EPICS) + * + * Copyright 1991, the Regents of the University of California, + * and the University of Chicago Board of Governors. + * + * This software was produced under U.S. Government contracts: + * (W-7405-ENG-36) at the Los Alamos National Laboratory, + * and (W-31-109-ENG-38) at Argonne National Laboratory. + * + * Initial development by: + * The Controls and Automation Group (AT-8) + * Ground Test Accelerator + * Accelerator Technology Division + * Los Alamos National Laboratory + * + * Co-developed with + * The Controls and Computing Group + * Accelerator Systems Division + * Advanced Photon Source + * Argonne National Laboratory + * + * Modification Log: + * ----------------- + * .00 10-16-97 mlr initialized from devOms58 + * .01 07-19-99 rls initialized from drvMM4000 + * ... + */ + +#include +#include +#include +#include /* jps: include for init_record wait */ + +#ifdef __cplusplus +extern "C" { +#include +} +#else +#include +#endif +#include /* for sysSymTbl*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "motorRecord.h" +#include "motor.h" +#include "motordevCom.h" +#include "drvMMCom.h" + +#define STATIC static + +#define ABS(f) ((f)>0 ? (f) : -(f)) +#define SIGN(f) (long)((f)>0 ? 1 : -1) +#define MAX(a,b) ((a)>(b) ? (a) : (b)) +#define MIN(a,b) ((a)<(b) ? (a) : (b)) + +/* ----------------Create the dsets for devMM3000----------------- */ +/* static long report(); */ +STATIC struct driver_table *drvtabptr; +STATIC long MM3000_init(int); +STATIC long MM3000_init_record(struct motorRecord *); +STATIC long MM3000_start_trans(struct motorRecord *); +STATIC long MM3000_build_trans(motor_cmnd, double *, struct motorRecord *); +STATIC long MM3000_end_trans(struct motorRecord *); + +struct motor_dset devMM3000 = +{ + {8, NULL, MM3000_init, MM3000_init_record, NULL}, + motor_update_values, + MM3000_start_trans, + MM3000_build_trans, + MM3000_end_trans +}; + + +/* --------------------------- program data --------------------- */ + +/* This table is used to define the command types */ + +static int MM3000_table[] = { + MOTION, /* MOVE_ABS */ + MOTION, /* MOVE_REL */ + MOTION, /* HOME_FOR */ + MOTION, /* HOME_REV */ + IMMEDIATE, /* LOAD_POS */ + IMMEDIATE, /* SET_VEL_BASE */ + IMMEDIATE, /* SET_VELOCITY */ + IMMEDIATE, /* SET_ACCEL */ + IMMEDIATE, /* GO */ + IMMEDIATE, /* SET_ENC_RATIO */ + INFO, /* GET_INFO */ + MOVE_TERM, /* STOP_AXIS */ + VELOCITY, /* JOG */ + IMMEDIATE, /* SET_PGAIN */ + IMMEDIATE, /* SET_IGAIN */ + IMMEDIATE, /* SET_DGAIN */ + IMMEDIATE, /* ENABLE_TORQUE */ + IMMEDIATE, /* DISABL_TORQUE */ + IMMEDIATE, /* PRIMITIVE */ + IMMEDIATE, /* SET_HIGH_LIMIT */ + IMMEDIATE, /* SET_LOW_LIMIT */ +}; + + +static struct board_stat **MM3000_cards; + +/* --------------------------- program data --------------------- */ + + +/* initialize device support for MM3000 stepper motor */ +STATIC long MM3000_init(int after) +{ + SYM_TYPE type; + long rtnval; + + if (after == 0) + { + rtnval = symFindByNameEPICS(sysSymTbl, "_MM3000_access", + (char **) &drvtabptr, &type); + if (rtnval != OK) + return(rtnval); + /* + IF before DB initialization. + Initialize MM3000 driver (i.e., call init()). See comment in + drvMM3000.c init(). + ENDIF + */ + (drvtabptr->init)(); + } + + rtnval = motor_init_com(after, *drvtabptr->cardcnt_ptr, drvtabptr, &MM3000_cards); + return(rtnval); +} + + +/* initialize a record instance */ +STATIC long MM3000_init_record(struct motorRecord *mr) +{ + return(motor_init_record_com(mr, *drvtabptr->cardcnt_ptr, drvtabptr, MM3000_cards)); +} + + +/* start building a transaction */ +STATIC long MM3000_start_trans(struct motorRecord *mr) +{ + return(motor_start_trans_com(mr, MM3000_cards, NULL)); +} + + +/* end building a transaction */ +STATIC long MM3000_end_trans(struct motorRecord *mr) +{ + struct motor_trans *trans = (struct motor_trans *) mr->dpvt; + struct mess_node *motor_call; + char *msgptr; + int last; + + /* Remove trailing ';'s from message. */ + motor_call = &(trans->motor_call); + msgptr = motor_call->message; + last = strlen(msgptr) - 1; + if (msgptr[last] == ';') + msgptr[last] = NULL; + + return(motor_end_trans_com(mr, drvtabptr, "\r")); +} + + +/* add a part to the transaction */ +STATIC long MM3000_build_trans(motor_cmnd command, double *parms, struct motorRecord *mr) +{ + struct motor_trans *trans = (struct motor_trans *) mr->dpvt; + struct mess_node *motor_call; + struct controller *brdptr; + struct MMcontroller *cntrl; + char buff[30]; + int axis, card; + int intval; + long rtnval; + + rtnval = OK; + buff[0] = '\0'; + + motor_call = &(trans->motor_call); + card = motor_call->card; + axis = motor_call->signal + 1; + brdptr = (*trans->tabptr->card_array)[card]; + if (brdptr == NULL) + return(rtnval = ERROR); + cntrl = brdptr->DevicePrivate; + + if (MM3000_table[command] > motor_call->type) + motor_call->type = MM3000_table[command]; + + if (trans->state != BUILD_STATE) + return(rtnval = ERROR); + + if (command == PRIMITIVE && mr->init != NULL && strlen(mr->init) != 0) + { + strcat(motor_call->message, mr->init); + strcat(motor_call->message, "\r"); + } + + switch (command) + { + case SET_PGAIN: + case SET_IGAIN: + case SET_DGAIN: + { + double temp_dbl; + + temp_dbl = parms[0] * 32767.0; + intval = NINT(temp_dbl); + break; + } + + case MOVE_ABS: + case MOVE_REL: + case HOME_FOR: + case HOME_REV: + case JOG: + if (strlen(mr->prem) != 0) + { + strcat(motor_call->message, mr->prem); + strcat(motor_call->message, ";"); + } + if (strlen(mr->post) != 0) + motor_call->postmsgptr = (char *) &mr->post; + default: + intval = NINT(parms[0]); + break; + } + + + switch (command) + { + case MOVE_ABS: + sprintf(buff, "%dPA%d;", axis, intval); + break; + case MOVE_REL: + sprintf(buff, "%dPR%d;", axis, intval); + break; + case HOME_FOR: + case HOME_REV: + sprintf(buff, "%dOR1;", axis); + break; + case LOAD_POS: + if (*parms == 0.0) + sprintf(buff, "%dDH", axis); + else + rtnval = ERROR; + break; + case SET_VEL_BASE: + if (cntrl->type[axis - 1] == STEPPER) + { + if (intval < 100) + intval = 100; + sprintf(buff, "%dVB%d;", axis, intval); + } + break; + case SET_VELOCITY: + if (cntrl->type[axis - 1] == STEPPER && intval < 100) + intval = 100; + sprintf(buff, "%dVA%d;", axis, intval); + break; + case SET_ACCEL: + /* + * The value passed is in steps/sec/sec. + * Convert to user units/sec/sec + */ + if (cntrl->type[axis - 1] == STEPPER && intval < 15000) + intval = 15000; + if (cntrl->type[axis - 1] == DC && intval < 250) + intval = 250; + sprintf(buff, "%dAC%d;", axis, intval); + break; + case GO: + /* + * The MM3000 starts moving immediately on move commands, GO command + * does nothing + */ + break; + case SET_ENC_RATIO: + /* MM3000 valid encoder ratio values < 10,000. */ + while (parms[0] > 10000.0 || parms[1] > 10000.0) + { + parms[0] /= 10; + parms[1] /= 10; + } + if (cntrl->type[axis - 1] == STEPPER) + sprintf(buff, "%dER%d:%d", axis, (int) parms[0], (int) parms[1]); + break; + case GET_INFO: + /* These commands are not actually done by sending a message, but + rather they will indirectly cause the driver to read the status + of all motors */ + break; + case STOP_AXIS: + sprintf(buff, "%dST;", axis); + break; + case JOG: + /* + * MM3000 does not have a jog command. Simulate with move absolute + * to the appropriate software limit. We can move to MM3000 soft limits. + * If the record soft limits are set tighter than the MM3000 limits + * the record will prevent JOG motion beyond its soft limits + */ + sprintf(buff, "%dVA%d;", axis, ABS(intval)); + strcat(motor_call->message, buff); + if (intval > 0) + sprintf(buff, "%dPA%d;", axis, (int) (mr->dhlm / mr->res)); + else + sprintf(buff, "%dPA%d;", axis, (int) (mr->dllm / mr->res)); + break; + case SET_PGAIN: + sprintf(buff, "%dKP%d;%dUF;", axis, intval, axis); + break; + case SET_IGAIN: + sprintf(buff, "%dKI%d;%dUF;", axis, intval, axis); + break; + case SET_DGAIN: + sprintf(buff, "%dKD%d;%dUF;", axis, intval, axis); + break; + case ENABLE_TORQUE: + sprintf(buff, "MO;"); + break; + case DISABL_TORQUE: + sprintf(buff, "MF;"); + break; + case SET_HIGH_LIMIT: + case SET_LOW_LIMIT: + sprintf(buff, "%dSL%d;", axis, intval); + break; + default: + rtnval = ERROR; + } + strcat(motor_call->message, buff); + + return(rtnval); +} diff --git a/motorApp/NewportSrc/devMM4000.c b/motorApp/NewportSrc/devMM4000.c new file mode 100644 index 00000000..1c241b76 --- /dev/null +++ b/motorApp/NewportSrc/devMM4000.c @@ -0,0 +1,371 @@ +/* +FILENAME... devMM4000.c +USAGE... Motor record device level support for Newport MM4000. + +Version: $Revision: 1.1 $ +Modified By: $Author: sluiter $ +Last Modified: $Date: 2000-02-08 22:18:52 $ +*/ + +/* + * Original Author: Mark Rivers + * Date: 10/20/97 + * + * Experimental Physics and Industrial Control System (EPICS) + * + * Copyright 1991, the Regents of the University of California, + * and the University of Chicago Board of Governors. + * + * This software was produced under U.S. Government contracts: + * (W-7405-ENG-36) at the Los Alamos National Laboratory, + * and (W-31-109-ENG-38) at Argonne National Laboratory. + * + * Initial development by: + * The Controls and Automation Group (AT-8) + * Ground Test Accelerator + * Accelerator Technology Division + * Los Alamos National Laboratory + * + * Co-developed with + * The Controls and Computing Group + * Accelerator Systems Division + * Advanced Photon Source + * Argonne National Laboratory + * + * Modification Log: + * ----------------- + * .01 10-20-97 mlr initialized from drvOms58 + * .02 10-30-97 mlr Replaced driver calls with gpipIO functions + * .03 10-30-98 mlr Minor code cleanup, improved formatting + * .04 02-01-99 mlr Added temporary fix to delay reading motor + * positions at the end of a move. + */ + + +#include +#include +#include +#include /* jps: include for init_record wait */ +#include + +#ifdef __cplusplus +extern "C" { +#include +} +#else +#include +#endif +#include /* for sysSymTbl*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "motorRecord.h" +#include "motor.h" +#include "motordevCom.h" +#include "drvMMCom.h" + +#define STATIC static + +#define ABS(f) ((f)>0 ? (f) : -(f)) +#define SIGN(f) (long)((f)>0 ? 1 : -1) +#define MAX(a,b) ((a)>(b) ? (a) : (b)) +#define MIN(a,b) ((a)<(b) ? (a) : (b)) + +/* ----------------Create the dsets for devMM4000----------------- */ +STATIC struct driver_table *drvtabptr; +STATIC long MM4000_init(int); +STATIC long MM4000_init_record(struct motorRecord *); +STATIC long MM4000_start_trans(struct motorRecord *); +STATIC long MM4000_build_trans(motor_cmnd, double *, struct motorRecord *); +STATIC long MM4000_end_trans(struct motorRecord *); + +struct motor_dset devMM4000 = +{ + {8, NULL, MM4000_init, MM4000_init_record, NULL}, + motor_update_values, + MM4000_start_trans, + MM4000_build_trans, + MM4000_end_trans +}; + + +/* --------------------------- program data --------------------- */ + +/* This table is used to define the command types */ + +static int MM4000_table[] = { + MOTION, /* MOVE_ABS */ + MOTION, /* MOVE_REL */ + MOTION, /* HOME_FOR */ + MOTION, /* HOME_REV */ + IMMEDIATE, /* LOAD_POS */ + IMMEDIATE, /* SET_VEL_BASE */ + IMMEDIATE, /* SET_VELOCITY */ + IMMEDIATE, /* SET_ACCEL */ + IMMEDIATE, /* GO */ + IMMEDIATE, /* SET_ENC_RATIO */ + INFO, /* GET_INFO */ + MOVE_TERM, /* STOP_AXIS */ + VELOCITY, /* JOG */ + IMMEDIATE, /* SET_PGAIN */ + IMMEDIATE, /* SET_IGAIN */ + IMMEDIATE, /* SET_DGAIN */ + IMMEDIATE, /* ENABLE_TORQUE */ + IMMEDIATE, /* DISABL_TORQUE */ + IMMEDIATE, /* PRIMITIVE */ + IMMEDIATE, /* SET_HIGH_LIMIT */ + IMMEDIATE /* SET_LOW_LIMIT */ +}; + + +static struct board_stat **MM4000_cards; + +/* --------------------------- program data --------------------- */ + + +/* initialize device support for MM4000 stepper motor */ +STATIC long MM4000_init(int after) +{ + SYM_TYPE type; + long rtnval; + + if (after == 0) + { + rtnval = symFindByNameEPICS(sysSymTbl, "_MM4000_access", + (char **) &drvtabptr, &type); + if (rtnval != OK) + return(rtnval); + /* + IF before DB initialization. + Initialize MM4000 driver (i.e., call init()). See comment in + drvMM4000.c init(). + ENDIF + */ + (drvtabptr->init)(); + } + + rtnval = motor_init_com(after, *drvtabptr->cardcnt_ptr, drvtabptr, &MM4000_cards); + return(rtnval); +} + + +/* initialize a record instance */ +STATIC long MM4000_init_record(struct motorRecord *mr) +{ + return(motor_init_record_com(mr, *drvtabptr->cardcnt_ptr, drvtabptr, MM4000_cards)); +} + + +/* start building a transaction */ +STATIC long MM4000_start_trans(struct motorRecord *mr) +{ + return(motor_start_trans_com(mr, MM4000_cards, NULL)); +} + + +/* end building a transaction */ +STATIC long MM4000_end_trans(struct motorRecord *mr) +{ + return(motor_end_trans_com(mr, drvtabptr, "\r")); +} + + +/* add a part to the transaction */ +STATIC long MM4000_build_trans(motor_cmnd command, double *parms, struct motorRecord *mr) +{ + struct motor_trans *trans = (struct motor_trans *) mr->dpvt; + struct mess_node *motor_call; + struct controller *brdptr; + struct mess_info *motor_info; + struct MMcontroller *cntrl; + char buff[110]; + int axis, card, maxdigits, size; + double dval, cntrl_units; + long rtnval; + + rtnval = OK; + buff[0] = '\0'; + dval = *parms; + + motor_call = &(trans->motor_call); + card = motor_call->card; + axis = motor_call->signal + 1; + brdptr = (*trans->tabptr->card_array)[card]; + if (brdptr == NULL) + return(rtnval = ERROR); + + cntrl = (struct MMcontroller *) brdptr->DevicePrivate; + cntrl_units = dval * cntrl->drive_resolution[axis - 1]; + maxdigits = cntrl->res_decpts[axis - 1]; + + if (MM4000_table[command] > motor_call->type) + motor_call->type = MM4000_table[command]; + + if (trans->state != BUILD_STATE) + return(rtnval = ERROR); + + if (command == PRIMITIVE && mr->init != NULL && strlen(mr->init) != 0) + { + strcat(motor_call->message, mr->init); + strcat(motor_call->message, "\r"); + } + + switch (command) + { + case MOVE_ABS: + case MOVE_REL: + case HOME_FOR: + case HOME_REV: + case JOG: + if (strlen(mr->prem) != 0) + { + strcat(motor_call->message, mr->prem); + strcat(motor_call->message, ";"); + } + if (strlen(mr->post) != 0) + motor_call->postmsgptr = (char *) &mr->post; + break; + + default: + break; + } + + + switch (command) + { + case MOVE_ABS: + sprintf(buff, "%dPA%.*f;", axis, maxdigits, cntrl_units); + break; + + case MOVE_REL: + sprintf(buff, "%dPR%.*f;", axis, maxdigits, cntrl_units); + break; + + case HOME_FOR: + case HOME_REV: + sprintf(buff, "%dOR;", axis); + break; + + case LOAD_POS: + if (cntrl->model == MM4000) + sprintf(buff, "%dSH%.*f;%dDH;%dSH%.*f", axis, maxdigits, cntrl_units, + axis, axis, maxdigits, cntrl->home_preset[axis - 1]); + break; + + case SET_VEL_BASE: + break; /* MM4000 does not use base velocity */ + + case SET_VELOCITY: + sprintf(buff, "%dVA%.*f;", axis, maxdigits, cntrl_units); + break; + + case SET_ACCEL: + /* + * The value passed is in steps/sec/sec. + * Convert to user units/sec/sec + */ + sprintf(buff, "%dAC%.*f;", axis, maxdigits, cntrl_units); + break; + + case GO: + /* + * The MM4000 starts moving immediately on move commands, GO command + * does nothing + */ + break; + + case SET_ENC_RATIO: + /* + * The MM4000 does not have the concept of encoder ratio, ignore this + * command + */ + break; + + case GET_INFO: + /* These commands are not actually done by sending a message, but + rather they will indirectly cause the driver to read the status + of all motors */ + break; + + case STOP_AXIS: + sprintf(buff, "%dST;", axis); + break; + + case JOG: + /* + * MM4000 does not have a jog command. Simulate with move absolute + * to the appropriate software limit. We can move to MM4000 soft limits. + * If the record soft limits are set tighter than the MM4000 limits + * the record will prevent JOG motion beyond its soft limits + */ + sprintf(buff, "%dVA%.*f;", axis, maxdigits, ABS(cntrl_units)); + strcat(motor_call->message, buff); + if (dval > 0.) + sprintf(buff, "%dPA%.*f;", axis, maxdigits, mr->dhlm); + else + sprintf(buff, "%dPA%.*f;", axis, maxdigits, mr->dllm); + break; + + case SET_PGAIN: + sprintf(buff, "%dKP%f;%dUF;", axis, dval, axis); + break; + + case SET_IGAIN: + sprintf(buff, "%dKI%f;%dUF;", axis, dval, axis); + break; + + case SET_DGAIN: + sprintf(buff, "%dKD%f;%dUF;", axis, dval, axis); + break; + + case ENABLE_TORQUE: + sprintf(buff, "MO;"); + break; + + case DISABL_TORQUE: + sprintf(buff, "MF;"); + break; + + case SET_HIGH_LIMIT: + motor_info = &(*trans->tabptr->card_array)[card]->motor_info[axis - 1]; + trans->state = IDLE_STATE; /* No command sent to the controller. */ + + if (cntrl_units > motor_info->high_limit) + { + mr->dhlm = motor_info->high_limit; + rtnval = ERROR; + } + break; + + case SET_LOW_LIMIT: + motor_info = &(*trans->tabptr->card_array)[card]->motor_info[axis - 1]; + trans->state = IDLE_STATE; /* No command sent to the controller. */ + + if (cntrl_units < motor_info->low_limit) + { + mr->dllm = motor_info->low_limit; + rtnval = ERROR; + } + break; + + default: + rtnval = ERROR; + } + + size = strlen(buff); + if (size > sizeof(buff) || (strlen(motor_call->message) + size) > MAX_MSG_SIZE) + logMsg((char *) "devMM4000.c:MM4000_build_trans(): buffer overflow.\n", + 0, 0, 0, 0, 0, 0); + else + strcat(motor_call->message, buff); + + return(rtnval); +} diff --git a/motorApp/NewportSrc/devNewport.dbd b/motorApp/NewportSrc/devNewport.dbd new file mode 100644 index 00000000..f6b95c59 --- /dev/null +++ b/motorApp/NewportSrc/devNewport.dbd @@ -0,0 +1,6 @@ +# Newport MM3000 and MM4000/5 Driver support. +device(motor,VME_IO,devMM3000,"MM3000") +device(motor,VME_IO,devMM4000,"MM4000") +driver(drvMM3000) +driver(drvMM4000) + diff --git a/motorApp/NewportSrc/drvMM3000.c b/motorApp/NewportSrc/drvMM3000.c new file mode 100644 index 00000000..86af819f --- /dev/null +++ b/motorApp/NewportSrc/drvMM3000.c @@ -0,0 +1,759 @@ +/* +FILENAME... drvMM3000.c +USAGE... Motor record driver level support for Newport MM3000. + +Version: $Revision: 1.1 $ +Modified By: $Author: sluiter $ +Last Modified: $Date: 2000-02-08 22:18:52 $ +*/ + +/* + * Original Author: Mark Rivers + * Date: 10/20/97 + * Current Author: Ron Sluiter + * + * Experimental Physics and Industrial Control System (EPICS) + * + * Copyright 1991, the Regents of the University of California, + * and the University of Chicago Board of Governors. + * + * This software was produced under U.S. Government contracts: + * (W-7405-ENG-36) at the Los Alamos National Laboratory, + * and (W-31-109-ENG-38) at Argonne National Laboratory. + * + * Initial development by: + * The Controls and Automation Group (AT-8) + * Ground Test Accelerator + * Accelerator Technology Division + * Los Alamos National Laboratory + * + * Co-developed with + * The Controls and Computing Group + * Accelerator Systems Division + * Advanced Photon Source + * Argonne National Laboratory + * + * Modification Log: + * ----------------- + * .01 10-20-97 mlr initialized from drvOms58 + * .02 10-30-97 mlr Replaced driver calls with gpipIO functions + * .03 10-30-98 mlr Minor code cleanup, improved formatting + * .04 02-01-99 mlr Added temporary fix to delay reading motor + * positions at the end of a move. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "motor.h" +#include "drvMMCom.h" +#include "gpibIO.h" +#include "serialIO.h" + +#define STATIC static + +#define READ_RESOLUTION "TU;\r" +#define READ_STATUS "MS;\r" +#define READ_POSITION "TP;\r" +#define STOP_ALL "ST\r" +#define MOTOR_ON "MO;\r" +#define REMOTE_MODE "MR;\r" +#define GET_IDENT "VE\r" + +#define INPUT_TERMINATOR '\n' + +/* Status byte bits */ +#define M_AXIS_MOVING 0x01 +#define M_MOTOR_POWER 0x02 +#define M_MOTOR_DIRECTION 0x04 +#define M_PLUS_LIMIT 0x08 +#define M_MINUS_LIMIT 0x10 +#define M_HOME_SIGNAL 0x20 + +#define MM3000_NUM_CARDS 4 +#define BUFF_SIZE 100 /* Maximum length of string to/from MM3000 */ + +/*----------------debugging-----------------*/ +#ifdef DEBUG + #define Debug(l, f, args...) { if(l<=drvMM3000debug) printf(f,## args); } +#else + #define Debug(l, f, args...) +#endif + +int MM3000_num_cards = 0; +volatile int drvMM3000debug = 0; + +/* Local data required for every driver; see "motordrvComCode.h" */ +#include "motordrvComCode.h" + +/* This is a temporary fix to introduce a delayed reading of the motor + * position after a move completes + */ +volatile double drvMM3000ReadbackDelay = 0.; + + +/*----------------functions-----------------*/ +STATIC int recv_mess(int card, char *com, int flag); +STATIC int send_mess(int card, char const *com, char c); +STATIC int set_status(int card, int signal); +static long report(int level); +static long init(); +STATIC int motor_init(); +STATIC void query_done(int, int, struct mess_node *); + +/*----------------functions-----------------*/ + +struct driver_table MM3000_access = +{ + motor_init, + motor_send, + motor_free, + motor_card_info, + motor_axis_info, + &mess_queue, + &queue_lock, + &free_list, + &freelist_lock, + &motor_sem, + &motor_state, + &total_cards, + &any_motor_in_motion, + send_mess, + recv_mess, + set_status, + query_done, + NULL, + &initialized +}; + +struct +{ + long number; +#ifdef __cplusplus + long (*report) (int); + long (*init) (void); +#else + DRVSUPFUN report; + DRVSUPFUN init; +#endif +} drvMM3000 = {2, report, init}; + + +/********************************************************* + * Print out driver status report + *********************************************************/ +static long report(int level) +{ + int card; + + if (MM3000_num_cards <=0) + printf(" No MM3000 controllers configured.\n"); + else + { + for (card = 0; card < MM3000_num_cards; card++) + { + struct controller *brdptr = motor_state[card]; + + if (brdptr == NULL) + printf(" MM3000 controller %d connection failed.\n", card); + else + { + struct MMcontroller *cntrl; + + cntrl = (struct MMcontroller *) brdptr->DevicePrivate; + switch (cntrl->port_type) + { + case RS232_PORT: + printf(" MM3000 controller %d port type = RS-232, id: %s \n", + card, + brdptr->ident); + break; + case GPIB_PORT: + printf(" MM3000 controller %d port type = GPIB, id: %s \n", + card, + brdptr->ident); + break; + default: + printf(" MM3000 controller %d port type = Unknown, id: %s \n", + card, + brdptr->ident); + break; + } + } + } + } + return (0); +} + + +static long init() +{ + /* + * We cannot call motor_init() here, because that function can do GPIB I/O, + * and hence requires that the drvGPIB have already been initialized. + * That cannot be guaranteed, so we need to call motor_init from device + * support + */ + /* Check for setup */ + if (MM3000_num_cards <= 0) + { + Debug(1, "init(): MM3000 driver disabled. MM3000Setup() missing from startup script.\n"); + } + return ((long) 0); +} + + +STATIC void query_done(int card, int axis, struct mess_node *nodeptr) +{ +} + + +/************************************************************** + * Parse status and position strings for a card and signal + * set_status() + ************************************************************/ + +STATIC int set_status(int card, int signal) +{ + struct MMcontroller *cntrl; + struct mess_node *nodeptr; + register struct mess_info *motor_info; + /* Message parsing variables */ + char *cptr, *tok_save; + char inbuff[BUFF_SIZE], outbuff[BUFF_SIZE]; + int itera, status, rtn_state; + double motorData; + + recv_mess(card, inbuff, FLUSH); + sprintf(outbuff, "%dMS\r", signal + 1); + send_mess(card, outbuff, NULL); + + itera = 0; + do + { + recv_mess(card, inbuff, 1); + itera++; + if (itera < 10 && strlen(inbuff) == 0) + { + Debug(2, "set_status(): empty MS response message.\n"); + send_mess(card, outbuff, NULL); + } + } while((strlen(inbuff) != 1) && itera < 10); + + if (itera == 10) + { + Debug(4, "set_status(): MS response error.\n"); + return(0); + } + status = inbuff[0]; + Debug(5, "set_status(): status byte = %x\n", status); + + cntrl = (struct MMcontroller *) motor_state[card]->DevicePrivate; + motor_info = &(motor_state[card]->motor_info[signal]); + nodeptr = motor_info->motor_motion; + + if (status & M_MOTOR_DIRECTION) + motor_info->status |= RA_DIRECTION; + else + motor_info->status &= ~RA_DIRECTION; + + if (status & M_AXIS_MOVING) + motor_info->status &= ~RA_DONE; + else + { + motor_info->status |= RA_DONE; +/* TEMPORARY FIX, Mark Rivers, 2/1/99. The MM3000 has reported that the + * motor is done moving, which means that the "jerk time" is done. However, + * the axis can still be settling. For now we put in a delay and poll the + * motor position again. This is not a good long-term solution. + */ + if (motor_info->pid_present == YES && drvMM3000ReadbackDelay != 0.) + { + taskDelay((int)(drvMM3000ReadbackDelay * sysClkRateGet())); + send_mess(card, READ_POSITION, NULL); + recv_mess(card, cntrl->position_string, 1); + } + } + + if ((status & M_PLUS_LIMIT) || (status & M_MINUS_LIMIT)) + motor_info->status |= RA_OVERTRAVEL; + else + motor_info->status &= ~RA_OVERTRAVEL; + + if (status & M_HOME_SIGNAL) + motor_info->status |= RA_HOME; + else + motor_info->status &= ~RA_HOME; + + if (status & M_MOTOR_POWER) + motor_info->status &= ~EA_POSITION; + else + motor_info->status |= EA_POSITION; + + /* encoder status */ + motor_info->status &= ~EA_SLIP; + motor_info->status &= ~EA_SLIP_STALL; + motor_info->status &= ~EA_HOME; + + sprintf(outbuff, "%dTP\r", signal + 1); + send_mess(card, outbuff, NULL); + recv_mess(card, inbuff, 1); + + tok_save = NULL; + cptr = strtok_r(inbuff, " ", &tok_save); + motorData = atof(cptr); + + if (motorData == motor_info->position) + motor_info->no_motion_count++; + else + { + motor_info->position = (int32_t) motorData; + if (motor_state[card]->motor_info[signal].encoder_present == YES) + motor_info->encoder_position = (int32_t) motorData; + else + motor_info->encoder_position = 0; + + motor_info->no_motion_count = 0; + } + + motor_info->status &= ~RA_PROBLEM; + + /* Parse motor velocity? */ + /* NEEDS WORK */ + + motor_info->velocity = 0; + + if (!(motor_info->status & RA_DIRECTION)) + motor_info->velocity *= -1; + + rtn_state = (!motor_info->no_motion_count || + (motor_info->status & (RA_OVERTRAVEL | RA_DONE | RA_PROBLEM))) ? 1 : 0; + + /* Test for post-move string. */ + if ((motor_info->status & RA_DONE || motor_info->status & RA_OVERTRAVEL) && + nodeptr != 0 && nodeptr->postmsgptr != 0) + { + strcpy(outbuff, nodeptr->postmsgptr); + strcat(outbuff, "\r"); + send_mess(card, outbuff, NULL); + nodeptr->postmsgptr = NULL; + } + + return (rtn_state); +} + + +/*****************************************************/ +/* send a message to the MM3000 board */ +/* send_mess() */ +/*****************************************************/ +STATIC int send_mess(int card, char const *com, char inchar) +{ + struct MMcontroller *cntrl; + + if (strlen(com) > MAX_MSG_SIZE) + { + logMsg((char *) "drvMM3000:send_mess(); message size violation.\n", + 0, 0, 0, 0, 0, 0); + return (-1); + } + + if (!motor_state[card]) + { + logMsg((char *) "drvMM3000:send_mess() - invalid card #%d\n", card, + 0, 0, 0, 0, 0); + return (-1); + } + + if (inchar != (char) NULL) + { + logMsg((char *) "drvMM3000:send_mess() - invalid argument = %c\n", inchar, + 0, 0, 0, 0, 0); + return (-1); + } + + Debug(2, "send_mess(): message = %s\n", com); + + cntrl = (struct MMcontroller *) motor_state[card]->DevicePrivate; + + if (cntrl->port_type == GPIB_PORT) + gpibIOSend(cntrl->gpibInfo, com, strlen(com), GPIB_TIMEOUT); + else + serialIOSend(cntrl->serialInfo, com, strlen(com), SERIAL_TIMEOUT); + + return (0); +} + + +/* + * FUNCTION... recv_mess(int card, char *com, int amount) + * + * INPUT ARGUMENTS... + * card - controller card # (0,1,...). + * *com - caller's response buffer. + * amount | -1 = flush controller's output buffer. + * | >= 1 = the # of command responses to retrieve into caller's + * response buffer. + * + * LOGIC... + * IF controller card does not exist. + * ERROR RETURN. + * ENDIF + * IF "amount" indicates buffer flush. + * WHILE characters left in input buffer. + * Call omsGet(). + * ENDWHILE + * ENDIF + * + * FOR each message requested (i.e. "amount"). + * Initialize head and tail pointers. + * Initialize retry counter and state indicator. + * WHILE retry count not exhausted, AND, state indicator is NOT at END. + * IF characters left in controller's input buffer. + * Process input character. + * ELSE IF command error occured - call omsError(). + * ERROR RETURN. + * ENDIF + * ENDWHILE + * IF retry count exhausted. + * Terminate receive buffer. + * ERROR RETURN. + * ENDIF + * Terminate command response. + * ENDFOR + * + * IF commands processed. + * Terminate response buffer. + * ELSE + * Clear response buffer. + * ENDIF + * NORMAL RETURN. + */ + +STATIC int recv_mess(int card, char *com, int amount) +{ + struct MMcontroller *cntrl; + int len=0; + + /* Check that card exists */ + if (!motor_state[card]) + return (-1); + + cntrl = (struct MMcontroller *) motor_state[card]->DevicePrivate; + if (amount == -1) + { + do + { + if (cntrl->port_type == GPIB_PORT) + len = gpibIORecv(cntrl->gpibInfo, com, BUFF_SIZE, INPUT_TERMINATOR, NULL); + else + len = serialIORecv(cntrl->serialInfo, com, BUFF_SIZE, INPUT_TERMINATOR, NULL); + Debug(2, "recv_mess(): flushed message = \"%s\"\n", com); + } while(len != 0); + } + else + { + if (cntrl->port_type == GPIB_PORT) + len = gpibIORecv(cntrl->gpibInfo, com, BUFF_SIZE, INPUT_TERMINATOR, GPIB_TIMEOUT); + else + len = serialIORecv(cntrl->serialInfo, com, BUFF_SIZE, INPUT_TERMINATOR, SERIAL_TIMEOUT); + } + + if (len == 0) + com[0] = '\0'; + else + com[len-2] = '\0'; /* Terminate at carriage return. */ + + Debug(2, "recv_mess(): message = \"%s\"\n", com); + return (len); +} + + +/*****************************************************/ +/* Setup system configuration */ +/* MM3000Setup() */ +/*****************************************************/ +int MM3000Setup(int num_cards, /* maximum number of controllers in system. */ + int num_channels, /* NOT Used. */ + int scan_rate) /* polling rate - 1/60 sec units. */ +{ + int itera; + + if (num_cards < 1 || num_cards > MM3000_NUM_CARDS) + MM3000_num_cards = MM3000_NUM_CARDS; + else + MM3000_num_cards = num_cards; + + /* Set motor polling task rate */ + if (scan_rate >= 1 && scan_rate <= sysClkRateGet()) + motor_scan_rate = sysClkRateGet() / scan_rate; + else + motor_scan_rate = SCAN_RATE; + + /* + * Allocate space for motor_state structures. Note this must be done + * before MM3000Config is called, so it cannot be done in motor_init() + * This means that we must allocate space for a card without knowing + * if it really exists, which is not a serious problem + */ + motor_state = (struct controller **) malloc(MM3000_num_cards * + sizeof(struct controller *)); + + for (itera = 0; itera < MM3000_num_cards; itera++) + motor_state[itera] = (struct controller *) NULL; + + return (0); +} + + +/*****************************************************/ +/* Configure a controller */ +/* MM3000Config() */ +/*****************************************************/ +int MM3000Config(int card, /* card being configured */ + int port_type, /* GPIB_PORT or RS232_PORT */ + int addr1, /* = link for GPIB or hideos_card for RS-232 */ + int addr2) /* GPIB address or hideos_task */ +{ + struct MMcontroller *cntrl; + + if (card < 0 || card >= MM3000_num_cards) + return (ERROR); + + motor_state[card] = (struct controller *) malloc(sizeof(struct controller)); + motor_state[card]->DevicePrivate = malloc(sizeof(struct MMcontroller)); + cntrl = (struct MMcontroller *) motor_state[card]->DevicePrivate; + + switch (port_type) + { + case GPIB_PORT: + cntrl->port_type = port_type; + cntrl->gpib_link = addr1; + cntrl->gpib_address = addr2; + break; + case RS232_PORT: + cntrl->port_type = port_type; + cntrl->serial_card = addr1; + strcpy(cntrl->serial_task, (char *) addr2); + break; + default: + return (ERROR); + } + return (0); +} + + + +/*****************************************************/ +/* initialize all software and hardware */ +/* This is called from the initialization routine in */ +/* device support. */ +/* motor_init() */ +/*****************************************************/ +STATIC int motor_init() +{ + struct controller *brdptr; + struct MMcontroller *cntrl; + int card_index, motor_index, arg3, arg4; + char axis_pos[BUFF_SIZE]; + char buff[BUFF_SIZE]; + char *tok_save, *bufptr; + int total_axis = 0; + int status; + BOOLEAN errind; + + initialized = ON; /* Indicate that driver is initialized. */ + + /* Check for setup */ + if (MM3000_num_cards <= 0) + return (ERROR); + + tok_save = NULL; + + for (card_index = 0; card_index < MM3000_num_cards; card_index++) + { + if (!motor_state[card_index]) + continue; + + brdptr = motor_state[card_index]; + total_cards = card_index + 1; + cntrl = (struct MMcontroller *) brdptr->DevicePrivate; + + /* Initialize communications channel */ + errind = OFF; + switch (cntrl->port_type) + { + case GPIB_PORT: + cntrl->gpibInfo = gpibIOInit(cntrl->gpib_link, + cntrl->gpib_address); + if (cntrl->gpibInfo == NULL) + errind = ON; + break; + case RS232_PORT: + cntrl->serialInfo = serialIOInit(cntrl->serial_card, + cntrl->serial_task); + if (cntrl->serialInfo == NULL) + errind = ON; + break; + } + + if (errind == OFF) + { + /* Send a message to the board, see if it exists */ + /* flush any junk at input port - should not be any data available */ + do + recv_mess(card_index, buff, FLUSH); + while (strlen(buff) != 0); + + send_mess(card_index, GET_IDENT, NULL); + status = recv_mess(card_index, axis_pos, 1); + /* Return value is length of response string */ + } + + if (errind == OFF && status > 0) + { + brdptr->localaddr = (char *) NULL; + brdptr->motor_in_motion = 0; + send_mess(card_index, STOP_ALL, NULL); /* Stop all motors */ + send_mess(card_index, GET_IDENT, NULL); /* Read controller ID string */ + recv_mess(card_index, buff, 1); + strcpy(brdptr->ident, &buff[0]); /* Skip "VE" */ + + send_mess(card_index, "RC\r", NULL); + recv_mess(card_index, buff, 1); + bufptr = strtok_r(buff, "=", &tok_save); + bufptr = strtok_r(NULL, " ", &tok_save); + + /* The return string will tell us how many axes this controller has */ + for (total_axis = 0; bufptr != NULL; total_axis++) + { + if (strcmp(bufptr, "unused") == 0) + { + cntrl->type[total_axis] = UNUSED; + bufptr = NULL; + } + else + { + if (strcmp(bufptr, "stepper1.5M") == 0) + cntrl->type[total_axis] = STEPPER; + else if (strcmp(bufptr, "dc") == 0) + cntrl->type[total_axis] = DC; + else + logMsg((char *) "drvMM3000:motor_init() - invalid RC response = %s\n", + bufptr, 0, 0, 0, 0, 0); + + bufptr = strtok_r(NULL, "=", &tok_save); + bufptr = strtok_r(NULL, " ", &tok_save); + } + + /* Initialize. */ + brdptr->motor_info[total_axis].motor_motion = NULL; + } + + brdptr->total_axis = total_axis; + + for (motor_index = 0; motor_index < total_axis; motor_index++) + { + struct mess_info *motor_info = &brdptr->motor_info[motor_index]; + + motor_info->status = 0; + motor_info->no_motion_count = 0; + motor_info->encoder_position = 0; + motor_info->position = 0; + + if (cntrl->type[total_axis] == DC) + motor_info->encoder_present = YES; + else + { + double pre_position, mod_position; + int fm_value; + + sprintf(buff, "%dFM?\r", motor_index + 1); + send_mess(card_index, buff, NULL); + recv_mess(card_index, buff, 1); + fm_value = atoi(&buff[3]); + + sprintf(buff, "%dTP\r", motor_index + 1); + send_mess(card_index, buff, NULL); + recv_mess(card_index, buff, 1); + pre_position = atof(&buff[3]); + + fm_value = (fm_value == 0) ? 1 : 0; + + sprintf(buff, "%dFM%d\r", motor_index + 1, fm_value); + send_mess(card_index, buff, NULL); + + sprintf(buff, "%dTP\r", motor_index + 1); + send_mess(card_index, buff, NULL); + recv_mess(card_index, buff, 1); + mod_position = atof(&buff[3]); + + fm_value = (fm_value == 0) ? 1 : 0; + + sprintf(buff, "%dFM%d\r", motor_index + 1, fm_value); + send_mess(card_index, buff, NULL); + + if (pre_position == mod_position) + motor_info->encoder_present = NO; + else + motor_info->encoder_present = YES; + } + + if (motor_info->encoder_present == YES) + { + motor_info->status |= EA_PRESENT; + motor_info->pid_present = YES; + motor_info->status |= GAIN_SUPPORT; + } + + set_status(card_index, motor_index); /* Read status of each motor */ + } + } + else + motor_state[card_index] = (struct controller *) NULL; + } + + motor_sem = semBCreate(SEM_Q_PRIORITY, SEM_EMPTY); + any_motor_in_motion = 0; + + FASTLOCKINIT(&queue_lock); + FASTLOCK(&queue_lock); + mess_queue.head = (struct mess_node *) NULL; + mess_queue.tail = (struct mess_node *) NULL; + FASTUNLOCK(&queue_lock); + + FASTLOCKINIT(&freelist_lock); + FASTLOCK(&freelist_lock); + free_list.head = (struct mess_node *) NULL; + free_list.tail = (struct mess_node *) NULL; + FASTUNLOCK(&freelist_lock); + + if (sizeof(int) >= sizeof(char *)) + { + arg3 = (int) (&MM3000_access); + arg4 = 0; + } + else + { + arg3 = (int) ((long) &MM3000_access >> 16); + arg4 = (int) ((long) &MM3000_access & 0xFFFF); + } + taskSpawn((char *) "MM3000_motor", 64, VX_FP_TASK | VX_STDIO, 5000, motor_task, + motor_scan_rate, arg3, arg4, 0, 0, 0, 0, 0, 0, 0); + return (0); +} + +/*---------------------------------------------------------------------*/ diff --git a/motorApp/NewportSrc/drvMM4000.c b/motorApp/NewportSrc/drvMM4000.c new file mode 100644 index 00000000..05d4b4bc --- /dev/null +++ b/motorApp/NewportSrc/drvMM4000.c @@ -0,0 +1,769 @@ +/* +FILENAME... drvMM4000.c +USAGE... Motor record driver level support for Newport MM4000. + +Version: $Revision: 1.1 $ +Modified By: $Author: sluiter $ +Last Modified: $Date: 2000-02-08 22:18:53 $ +*/ + +/* + * Original Author: Mark Rivers + * Date: 10/20/97 + * Current Author: Ron Sluiter + * + * Experimental Physics and Industrial Control System (EPICS) + * + * Copyright 1991, the Regents of the University of California, + * and the University of Chicago Board of Governors. + * + * This software was produced under U.S. Government contracts: + * (W-7405-ENG-36) at the Los Alamos National Laboratory, + * and (W-31-109-ENG-38) at Argonne National Laboratory. + * + * Initial development by: + * The Controls and Automation Group (AT-8) + * Ground Test Accelerator + * Accelerator Technology Division + * Los Alamos National Laboratory + * + * Co-developed with + * The Controls and Computing Group + * Accelerator Systems Division + * Advanced Photon Source + * Argonne National Laboratory + * + * Modification Log: + * ----------------- + * .01 10-20-97 mlr initialized from drvOms58 + * .02 10-30-97 mlr Replaced driver calls with gpipIO functions + * .03 10-30-98 mlr Minor code cleanup, improved formatting + * .04 02-01-99 mlr Added temporary fix to delay reading motor + * positions at the end of a move. + * .05 10-13-99 rls modified for standardized motor record. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "motor.h" +#include "drvMMCom.h" +#include "gpibIO.h" +#include "serialIO.h" + +#define STATIC static + +#define READ_RESOLUTION "TU;\r" +#define READ_STATUS "MS;\r" +#define READ_POSITION "TP;\r" +#define STOP_ALL "ST;\r" +#define MOTOR_ON "MO;\r" +#define GET_IDENT "VE;\r" + +#define INPUT_TERMINATOR '\r' + +/* Status byte bits */ +#define M_AXIS_MOVING 0x01 +#define M_MOTOR_POWER 0x02 +#define M_MOTOR_DIRECTION 0x04 +#define M_PLUS_LIMIT 0x08 +#define M_MINUS_LIMIT 0x10 +#define M_HOME_SIGNAL 0x20 + +#define MM4000_NUM_CARDS 4 +#define BUFF_SIZE 100 /* Maximum length of string to/from MM4000 */ + +/*----------------debugging-----------------*/ +#ifdef DEBUG + #define Debug(l, f, args...) { if(l<=drvMM4000debug) printf(f,## args); } +#else + #define Debug(l, f, args...) +#endif + +/* --- Local data. --- */ +int MM4000_num_cards = 0; +volatile int drvMM4000debug = 0; + +/* Local data required for every driver; see "motordrvComCode.h" */ +#include "motordrvComCode.h" + +/* This is a temporary fix to introduce a delayed reading of the motor + * position after a move completes + */ +volatile double drvMM4000ReadbackDelay = 0.; + + +/*----------------functions-----------------*/ +STATIC int recv_mess(int, char *, int); +STATIC int send_mess(int card, char const *com, char c); +STATIC void start_status(int card); +STATIC int set_status(int card, int signal); +static long report(int level); +static long init(); +STATIC int motor_init(); +STATIC void query_done(int, int, struct mess_node *); + +/*----------------functions-----------------*/ + +struct driver_table MM4000_access = +{ + motor_init, + motor_send, + motor_free, + motor_card_info, + motor_axis_info, + &mess_queue, + &queue_lock, + &free_list, + &freelist_lock, + &motor_sem, + &motor_state, + &total_cards, + &any_motor_in_motion, + send_mess, + recv_mess, + set_status, + query_done, + start_status, + &initialized +}; + +struct +{ + long number; +#ifdef __cplusplus + long (*report) (int); + long (*init) (void); +#else + DRVSUPFUN report; + DRVSUPFUN init; +#endif +} drvMM4000 = {2, report, init}; + + +/********************************************************* + * Print out driver status report + *********************************************************/ +static long report(int level) +{ + int card; + + if (MM4000_num_cards <=0) + printf(" No MM4000 controllers configured.\n"); + else + { + for (card = 0; card < MM4000_num_cards; card++) + { + struct controller *brdptr = motor_state[card]; + + if (brdptr == NULL) + printf(" MM4000 controller %d connection failed.\n", card); + else + { + struct MMcontroller *cntrl; + + cntrl = (struct MMcontroller *) brdptr->DevicePrivate; + switch (cntrl->port_type) + { + case RS232_PORT: + printf(" MM4000 controller %d port type = RS-232, id: %s \n", + card, + brdptr->ident); + break; + case GPIB_PORT: + printf(" MM4000 controller %d port type = GPIB, id: %s \n", + card, + brdptr->ident); + break; + default: + printf(" MM4000 controller %d port type = Unknown, id: %s \n", + card, + brdptr->ident); + break; + } + } + } + } + return (0); +} + + +static long init() +{ + /* + * We cannot call motor_init() here, because that function can do GPIB I/O, + * and hence requires that the drvGPIB have already been initialized. + * That cannot be guaranteed, so we need to call motor_init from device + * support + */ + /* Check for setup */ + if (MM4000_num_cards <= 0) + { + Debug(1, "init(): MM4000 driver disabled. MM4000Setup() missing from startup script.\n"); + } + return ((long) 0); +} + + +STATIC void query_done(int card, int axis, struct mess_node *nodeptr) +{ +} + + +/********************************************************* + * Read the status and position of all motors on a card + * start_status(int card) + * if card == -1 then start all cards + *********************************************************/ +STATIC void start_status(int card) +{ + struct MMcontroller *cntrl; + int itera, status; + + if (card >= 0) + { + cntrl = (struct MMcontroller *) motor_state[card]->DevicePrivate; + send_mess(card, READ_STATUS, NULL); + status = recv_mess(card, cntrl->status_string, 1); + if (status <= 0) + cntrl->status = COMM_ERR; + else + { + cntrl->status = NORMAL; + send_mess(card, READ_POSITION, NULL); + recv_mess(card, cntrl->position_string, 1); + } + } + else + { + /* + * For efficiency we send messages to all cards, then read all + * responses. This minimizes the latency due to processing on each card + */ + for (itera = 0; (itera < total_cards) && motor_state[itera]; itera++) + send_mess(itera, READ_STATUS, NULL); + for (itera = 0; (itera < total_cards) && motor_state[itera]; itera++) + { + cntrl = (struct MMcontroller *) motor_state[itera]->DevicePrivate; + status = recv_mess(itera, cntrl->status_string, 1); + if (status <= 0) + cntrl->status = COMM_ERR; + else + cntrl->status = NORMAL; + } + for (itera = 0; (itera < total_cards) && motor_state[itera]; itera++) + send_mess(itera, READ_POSITION, NULL); + for (itera = 0; (itera < total_cards) && motor_state[itera]; itera++) + { + cntrl = (struct MMcontroller *) motor_state[itera]->DevicePrivate; + recv_mess(itera, cntrl->position_string, 1); + } + } +} + + +/************************************************************** + * Parse status and position strings for a card and signal + * set_status() + ************************************************************/ + +STATIC int set_status(int card, int signal) +{ + struct MMcontroller *cntrl; + struct mess_node *nodeptr; + register struct mess_info *motor_info; + /* Message parsing variables */ + char *p, *tok_save; + char buff[BUFF_SIZE]; + int i, pos, status; + int rtn_state; + double motorData; + + cntrl = (struct MMcontroller *) motor_state[card]->DevicePrivate; + motor_info = &(motor_state[card]->motor_info[signal]); + nodeptr = motor_info->motor_motion; + + /* + * Parse the status string + * Status string format: 1MSx,2MSy,3MSz,... where x, y and z are the status + * bytes for the motors + */ + pos = signal*5 + 3; /* Offset in status string */ + status = cntrl->status_string[pos]; + Debug(5, "set_status(): status byte = %x\n", status); + + if (status & M_MOTOR_DIRECTION) + motor_info->status |= RA_DIRECTION; + else + motor_info->status &= ~RA_DIRECTION; + + if (status & M_AXIS_MOVING) + motor_info->status &= ~RA_DONE; + else + { + motor_info->status |= RA_DONE; +/* TEMPORARY FIX, Mark Rivers, 2/1/99. The MM4000 has reported that the + * motor is done moving, which means that the "jerk time" is done. However, + * the axis can still be settling. For now we put in a delay and poll the + * motor position again. This is not a good long-term solution. + */ + if (motor_info->pid_present == YES && drvMM4000ReadbackDelay != 0.) + { + taskDelay((int)(drvMM4000ReadbackDelay * sysClkRateGet())); + send_mess(card, READ_POSITION, NULL); + recv_mess(card, cntrl->position_string, 1); + } + } + + if ((status & M_PLUS_LIMIT) || (status & M_MINUS_LIMIT)) + motor_info->status |= RA_OVERTRAVEL; + else + motor_info->status &= ~RA_OVERTRAVEL; + + if (status & M_HOME_SIGNAL) + motor_info->status |= RA_HOME; + else + motor_info->status &= ~RA_HOME; + + if (status & M_MOTOR_POWER) + motor_info->status &= ~EA_POSITION; + else + motor_info->status |= EA_POSITION; + + /* encoder status */ + motor_info->status &= ~EA_SLIP; + motor_info->status &= ~EA_SLIP_STALL; + motor_info->status &= ~EA_HOME; + + /* + * Parse motor position + * Position string format: 1TP5.012,2TP1.123,3TP-100.567,... + * Skip to substring for this motor, convert to double + */ + + strcpy(buff, cntrl->position_string); + tok_save = NULL; + p = strtok_r(buff, ",", &tok_save); + for (i=0; idrive_resolution[signal]; + + if (motorData == motor_info->position) + motor_info->no_motion_count++; + else + { + motor_info->position = NINT(motorData); + if (motor_state[card]->motor_info[signal].encoder_present == YES) + motor_info->encoder_position = (int32_t) motorData; + else + motor_info->encoder_position = 0; + + motor_info->no_motion_count = 0; + } + + motor_info->status &= ~RA_PROBLEM; + + /* Parse motor velocity? */ + /* NEEDS WORK */ + + motor_info->velocity = 0; + + if (!(motor_info->status & RA_DIRECTION)) + motor_info->velocity *= -1; + + rtn_state = (!motor_info->no_motion_count || + (motor_info->status & (RA_OVERTRAVEL | RA_DONE | RA_PROBLEM))) ? 1 : 0; + + /* Test for post-move string. */ + if ((motor_info->status & RA_DONE || motor_info->status & RA_OVERTRAVEL) && + nodeptr != 0 && nodeptr->postmsgptr != 0) + { + strcpy(buff, nodeptr->postmsgptr); + strcat(buff, "\r"); + send_mess(card, buff, NULL); + nodeptr->postmsgptr = NULL; + } + + if (cntrl->status == COMM_ERR) + motor_info->status |= CNTRL_COMM_ERR; + else + motor_info->status &= ~CNTRL_COMM_ERR; + + return(rtn_state); +} + + +/*****************************************************/ +/* send a message to the MM4000 board */ +/* send_mess() */ +/*****************************************************/ +STATIC int send_mess(int card, char const *com, char inchar) +{ + struct MMcontroller *cntrl; + + if (strlen(com) > MAX_MSG_SIZE) + { + logMsg((char *) "drvMM4000.c:send_mess(); message size violation.\n", + 0, 0, 0, 0, 0, 0); + return (-1); + } + + if (!motor_state[card]) + { + logMsg((char *) "drvMM4000.c:send_mess() - invalid card #%d\n", card, + 0, 0, 0, 0, 0); + return (-1); + } + + if (inchar != (char) NULL) + { + logMsg((char *) "drvMM4000.c:send_mess() - invalid argument = %c\n", inchar, + 0, 0, 0, 0, 0); + return (-1); + } + + Debug(2, "send_mess(): message = %s\n", com); + + cntrl = (struct MMcontroller *) motor_state[card]->DevicePrivate; + + switch (cntrl->port_type) + { + case GPIB_PORT: + gpibIOSend(cntrl->gpibInfo, com, strlen(com), GPIB_TIMEOUT); + break; + case RS232_PORT: + serialIOSend(cntrl->serialInfo, com, strlen(com), SERIAL_TIMEOUT); + break; + } + return (0); +} + + +/*****************************************************/ +/* receive a message from the MM4000 board */ +/* recv_mess() */ +/*****************************************************/ +STATIC int recv_mess(int card, char *com, int flag) +{ + struct MMcontroller *cntrl; + int timeout; + int len=0; + + /* Check that card exists */ + if (!motor_state[card]) + return (-1); + + cntrl = (struct MMcontroller *) motor_state[card]->DevicePrivate; + + switch (cntrl->port_type) + { + case GPIB_PORT: + if (flag == FLUSH) + timeout = 0; + else + timeout = GPIB_TIMEOUT; + len = gpibIORecv(cntrl->gpibInfo, com, BUFF_SIZE, INPUT_TERMINATOR, + timeout); + break; + case RS232_PORT: + if (flag == FLUSH) + timeout = 0; + else + timeout = SERIAL_TIMEOUT; + len = serialIORecv(cntrl->serialInfo, com, BUFF_SIZE, + INPUT_TERMINATOR, timeout); + break; + } + + if (len == 0) + com[0] = '\0'; + else + com[len-1] = '\0'; + + Debug(2, "recv_mess(): message = \"%s\"\n", com); + return (len); +} + + +/*****************************************************/ +/* Setup system configuration */ +/* MM4000Setup() */ +/*****************************************************/ +int MM4000Setup(int num_cards, /* maximum number of controllers in system. */ + int num_channels, /* NOT Used. */ + int scan_rate) /* polling rate - 1/60 sec units. */ +{ + int itera; + + if (num_cards < 1 || num_cards > MM4000_NUM_CARDS) + MM4000_num_cards = MM4000_NUM_CARDS; + else + MM4000_num_cards = num_cards; + + /* Set motor polling task rate */ + if (scan_rate >= 1 && scan_rate <= sysClkRateGet()) + motor_scan_rate = sysClkRateGet() / scan_rate; + else + motor_scan_rate = SCAN_RATE; + + /* + * Allocate space for motor_state structures. Note this must be done + * before MM4000Config is called, so it cannot be done in motor_init() + * This means that we must allocate space for a card without knowing + * if it really exists, which is not a serious problem + */ + motor_state = (struct controller **) malloc(MM4000_num_cards * + sizeof(struct controller *)); + + for (itera = 0; itera < MM4000_num_cards; itera++) + motor_state[itera] = (struct controller *) NULL; + + return (0); +} + + +/*****************************************************/ +/* Configure a controller */ +/* MM4000Config() */ +/*****************************************************/ +int MM4000Config(int card, /* card being configured */ + int port_type, /* GPIB_PORT or RS232_PORT */ + int addr1, /* = link for GPIB or hideos_card for RS-232 */ + int addr2) /* GPIB address or hideos_task */ +{ + struct MMcontroller *cntrl; + + if (card < 0 || card >= MM4000_num_cards) + return (ERROR); + + motor_state[card] = (struct controller *) malloc(sizeof(struct controller)); + motor_state[card]->DevicePrivate = malloc(sizeof(struct MMcontroller)); + cntrl = (struct MMcontroller *) motor_state[card]->DevicePrivate; + + switch (port_type) + { + case GPIB_PORT: + cntrl->port_type = port_type; + cntrl->gpib_link = addr1; + cntrl->gpib_address = addr2; + break; + case RS232_PORT: + cntrl->port_type = port_type; + cntrl->serial_card = addr1; + strcpy(cntrl->serial_task, (char *) addr2); + break; + default: + return (ERROR); + } + return (0); +} + + + +/*****************************************************/ +/* initialize all software and hardware */ +/* This is called from the initialization routine in */ +/* device support. */ +/* motor_init() */ +/*****************************************************/ +STATIC int motor_init() +{ + struct controller *brdptr; + struct MMcontroller *cntrl; + int card_index, motor_index, arg3, arg4; + char axis_pos[BUFF_SIZE]; + char buff[BUFF_SIZE]; + char *tok_save, *pos_ptr; + int total_axis = 0; + int status, model_num, digits; + BOOLEAN errind; + + initialized = ON; /* Indicate that driver is initialized. */ + + /* Check for setup */ + if (MM4000_num_cards <= 0) + return (ERROR); + + for (card_index = 0; card_index < MM4000_num_cards; card_index++) + { + if (!motor_state[card_index]) + continue; + + brdptr = motor_state[card_index]; + total_cards = card_index + 1; + cntrl = (struct MMcontroller *) brdptr->DevicePrivate; + + /* Initialize communications channel */ + errind = OFF; + switch (cntrl->port_type) + { + case GPIB_PORT: + cntrl->gpibInfo = gpibIOInit(cntrl->gpib_link, + cntrl->gpib_address); + if (cntrl->gpibInfo == NULL) + errind = ON; + break; + case RS232_PORT: + cntrl->serialInfo = serialIOInit(cntrl->serial_card, + cntrl->serial_task); + if (cntrl->serialInfo == NULL) + errind = ON; + break; + } + + if (errind == OFF) + { + int retry = 0; + + /* Send a message to the board, see if it exists */ + /* flush any junk at input port - should not be any data available */ + do + recv_mess(card_index, buff, FLUSH); + while (strlen(buff) != 0); + + do + { + send_mess(card_index, READ_POSITION, NULL); + status = recv_mess(card_index, axis_pos, 1); + retry++; + /* Return value is length of response string */ + } while(status == 0 && retry < 3); + } + + if (errind == OFF && status > 0) + { + brdptr->localaddr = (char *) NULL; + brdptr->motor_in_motion = 0; + send_mess(card_index, STOP_ALL, NULL); /* Stop all motors */ + send_mess(card_index, GET_IDENT, NULL); /* Read controller ID string */ + recv_mess(card_index, buff, 1); + strcpy(brdptr->ident, &buff[2]); /* Skip "VE" */ + + /* Set Motion Master model indicator. */ + pos_ptr = strstr(brdptr->ident, "MM"); + model_num = atoi(pos_ptr + 2); + if (model_num == 4000) + cntrl->model = MM4000; + else if (model_num == 4005) + cntrl->model = MM4005; + else + { + logMsg((char *) "drvMM4000.c:motor_init() - invalid model = %s\n", (int) brdptr->ident, + 0, 0, 0, 0, 0); + return (ERROR); + } + + send_mess(card_index, READ_POSITION, NULL); + recv_mess(card_index, axis_pos, 1); + + /* The return string will tell us how many axes this controller has */ + for (total_axis = 0, tok_save = NULL, pos_ptr = strtok_r(axis_pos, ",", &tok_save); + pos_ptr != 0; pos_ptr = strtok_r(NULL, ",", &tok_save), total_axis++) + brdptr->motor_info[total_axis].motor_motion = NULL; + + brdptr->total_axis = total_axis; + + start_status(card_index); + for (motor_index = 0; motor_index < total_axis; motor_index++) + { + struct mess_info *motor_info = &brdptr->motor_info[motor_index]; + int loop_state; + + motor_info->status = 0; + motor_info->no_motion_count = 0; + motor_info->encoder_position = 0; + motor_info->position = 0; + + /* Determine if encoder present based on open/closed loop mode. */ + sprintf(buff, "%dTC\r", motor_index + 1); + send_mess(card_index, buff, NULL); + recv_mess(card_index, buff, 1); + loop_state = atoi(&buff[3]); /* Skip first 3 characters */ + if (loop_state != 0) + { + motor_info->encoder_present = YES; + motor_info->status |= EA_PRESENT; + motor_info->pid_present = YES; + motor_info->status |= GAIN_SUPPORT; + } + + /* Determine drive resolution. */ + sprintf(buff, "%dTU\r", motor_index + 1); + send_mess(card_index, buff, NULL); + recv_mess(card_index, buff, 1); + cntrl->drive_resolution[motor_index] = atof(&buff[3]); + + digits = -log10(cntrl->drive_resolution[motor_index]) + 2; + if (digits < 1) + digits = 1; + cntrl->res_decpts[motor_index] = digits; + + /* Save home preset position. */ + sprintf(buff, "%dXH\r", motor_index + 1); + send_mess(card_index, buff, NULL); + recv_mess(card_index, buff, 1); + cntrl->home_preset[motor_index] = atof(&buff[3]); + + /* Determine low limit */ + sprintf(buff, "%dTL\r", motor_index + 1); + send_mess(card_index, buff, NULL); + recv_mess(card_index, buff, 1); + motor_info->low_limit = atof(&buff[3]); + + /* Determine high limit */ + sprintf(buff, "%dTR\r", motor_index + 1); + send_mess(card_index, buff, NULL); + recv_mess(card_index, buff, 1); + motor_info->high_limit = atof(&buff[3]); + + set_status(card_index, motor_index); /* Read status of each motor */ + } + } + else + motor_state[card_index] = (struct controller *) NULL; + } + + motor_sem = semBCreate(SEM_Q_PRIORITY, SEM_EMPTY); + any_motor_in_motion = 0; + + FASTLOCKINIT(&queue_lock); + FASTLOCK(&queue_lock); + mess_queue.head = (struct mess_node *) NULL; + mess_queue.tail = (struct mess_node *) NULL; + FASTUNLOCK(&queue_lock); + + FASTLOCKINIT(&freelist_lock); + FASTLOCK(&freelist_lock); + free_list.head = (struct mess_node *) NULL; + free_list.tail = (struct mess_node *) NULL; + FASTUNLOCK(&freelist_lock); + + if (sizeof(int) >= sizeof(char *)) + { + arg3 = (int) (&MM4000_access); + arg4 = 0; + } + else + { + arg3 = (int) ((long) &MM4000_access >> 16); + arg4 = (int) ((long) &MM4000_access & 0xFFFF); + } + taskSpawn((char *) "MM4000_motor", 64, VX_FP_TASK | VX_STDIO, 5000, motor_task, + motor_scan_rate, arg3, arg4, 0, 0, 0, 0, 0, 0, 0); + return (0); +} + diff --git a/motorApp/NewportSrc/drvMMCom.h b/motorApp/NewportSrc/drvMMCom.h new file mode 100644 index 00000000..7e36cb66 --- /dev/null +++ b/motorApp/NewportSrc/drvMMCom.h @@ -0,0 +1,104 @@ +/* +FILENAME... drvMMCom.h +USAGE... This file contains Newport Motion Master (MM) driver "include" + information that is specific to Motion Master models 3000/4000. + +Version: $Revision: 1.1 $ +Modified By: $Author: sluiter $ +Last Modified: $Date: 2000-02-08 22:18:53 $ +*/ + +/* + * Original Author: Mark Rivers + * Current Author: Mark Rivers + * Date: 10/16/97 + * + * Experimental Physics and Industrial Control System (EPICS) + * + * Copyright 1991, the Regents of the University of California, + * and the University of Chicago Board of Governors. + * + * This software was produced under U.S. Government contracts: + * (W-7405-ENG-36) at the Los Alamos National Laboratory, + * and (W-31-109-ENG-38) at Argonne National Laboratory. + * + * Initial development by: + * The Controls and Automation Group (AT-8) + * Ground Test Accelerator + * Accelerator Technology Division + * Los Alamos National Laboratory + * + * Co-developed with + * The Controls and Computing Group + * Accelerator Systems Division + * Advanced Photon Source + * Argonne National Laboratory + * + * Modification Log: + * ----------------- + * .01 01-18-93 mlr initialized from drvOms58 + */ + +#ifndef INCdrvMMComh +#define INCdrvMMComh 1 + +#include "motordrvCom.h" + +#define MAX_UNIT_LENGTH 5 /* Maximum length of controller position units string. */ + +#define GPIB_PORT 0 +#define RS232_PORT 1 +#define GPIB_TIMEOUT 2000 /* Command timeout in msec */ +#define SERIAL_TIMEOUT 2000 /* Command timeout in msec */ + +enum MM_model +{ + MM4000, + MM4005 +}; + +enum MM_motor_type +{ + UNUSED, + STEPPER, + DC +}; + +enum MM_comm_status +{ + NORMAL, + COMM_ERR +}; + +#ifndef __cplusplus +typedef enum MM_model MM_model; +typedef enum MM_motor_type MM_motor_type; +typedef enum MM_comm_status MM_comm_status; +#endif + + +/* Motion Master specific data is stored in this structure. */ +struct MMcontroller +{ + int port_type; /* GPIB_PORT or RS232_PORT */ + struct serialInfo *serialInfo; /* For RS-232 */ + int gpib_link; + int gpib_address; + struct gpibInfo *gpibInfo; /* For GPIB */ + int serial_card; /* Card on which Hideos is running */ + char serial_task[20]; /* Hideos task name for serial port */ + char status_string[80]; /* String containing status of motors */ + char position_string[80]; /* String containing position of motors */ + MM_model model; /* Motion Master Model. */ + MM_motor_type type[4]; /* For MM3000 only; Motor type array. */ + /* For MM4000/5 only; controller resolution array (from TU command). + * Units are in [Controller EGU's / Record RAW units]. + */ + double drive_resolution[MAX_AXIS]; + int res_decpts[MAX_AXIS]; /* Drive resolution significant dec. pts. */ + double home_preset[MAX_AXIS]; /* Controller's home preset position (XF command). */ + MM_comm_status status; /* Controller communication status. */ +}; + +#endif /* INCdrvMMComh */ + diff --git a/motorApp/NewportSrc/gpibIO.c b/motorApp/NewportSrc/gpibIO.c new file mode 100644 index 00000000..9aa32e0c --- /dev/null +++ b/motorApp/NewportSrc/gpibIO.c @@ -0,0 +1,154 @@ +/* gpibIO.c - Routines for asynchronous EPICS GPIB I/O + * + * Author: Mark Rivers + * Date: October 30, 1997 + * + * Modification Log: + * ----------------- + * .01 30-Oct-1997 mlr Initial release + * + * These routines provide a simple interface to GPIB I/O. They hide the + * details of the driver. They are intended for use by applications which do + * simple synchronous reads and writes, and no control operations like Serial + * Poll. + */ + +#include +#include +#include +#include + +#define GPIB_SEND 0 +#define GPIB_RECEIVE 1 + +#ifdef NODEBUG +#define Debug(l,f,v) ; +#else +#define Debug(l,f,v) { if(l<=gpibIODebug) printf(f,v); } +#endif + +volatile int gpibIODebug = 0; + +extern struct drvGpibSet drvGpib; /* entry points to driver functions */ + +struct gpibMessage { + struct dpvtGpibHead head; + int function; + int address; + int timeout; + int status; + int buff_len; + int terminator; + SEM_ID semID; + char *buffer; +}; + +static int gpibIOCallback(struct gpibMessage *gpibMessage); + +struct gpibInfo *gpibIOInit(int link, int address) +{ + struct gpibInfo *gpibInfo; + gpibInfo = (struct gpibInfo *) malloc(sizeof(struct gpibInfo)); + gpibInfo->head.workStart = (int (*)()) gpibIOCallback; + gpibInfo->head.link = link; + gpibInfo->head.device = address; + gpibInfo->address = address; + gpibInfo->semID = semBCreate(SEM_Q_PRIORITY, SEM_EMPTY); + + (*(drvGpib.ioctl))(GPIB_IO, link, NULL, IBGENLINK, 0, NULL); + (*(drvGpib.ioctl))(GPIB_IO, link, NULL, IBGETLINK, 0, &gpibInfo->head.pibLink); + Debug(1, "gpibIOInit, address = %d\n", address); + return(gpibInfo); +} + + +int gpibIOSend(struct gpibInfo *info, char const *buffer, int buff_len, int timeout) +{ + struct gpibMessage *gpibMessage; + int status; + + gpibMessage = (struct gpibMessage *) malloc(sizeof(struct gpibMessage)); + gpibMessage->address = info->address; + gpibMessage->buffer = (char *) buffer; + gpibMessage->buff_len = buff_len; + gpibMessage->timeout = timeout; + gpibMessage->function = GPIB_SEND; + gpibMessage->head = info->head; + gpibMessage->semID = info->semID; + Debug(2, "gpibIOSend, calling driver, buffer = %s\n", buffer); + (*(drvGpib.qGpibReq))(gpibMessage, IB_Q_LOW); + semTake(gpibMessage->semID, WAIT_FOREVER); + status = gpibMessage->status; + free(gpibMessage); + return (status); +} + + +int gpibIORecv(struct gpibInfo *info, char *buffer, int buff_len, + int terminator, int timeout) +{ + struct gpibMessage *gpibMessage; + int status; + + gpibMessage = (struct gpibMessage *) malloc(sizeof(struct gpibMessage)); + gpibMessage->address = info->address; + gpibMessage->buffer = buffer; + gpibMessage->buff_len = buff_len; + gpibMessage->terminator = terminator; + gpibMessage->timeout = timeout; + gpibMessage->function = GPIB_RECEIVE; + gpibMessage->head = info->head; + gpibMessage->semID = info->semID; + Debug(2, "gpibIORecv, calling driver, address = %d\n", info->address); + (*(drvGpib.qGpibReq))(gpibMessage, IB_Q_LOW); + semTake(gpibMessage->semID, WAIT_FOREVER); + status = gpibMessage->status; + free(gpibMessage); + return (status); +} + + +static int gpibIOCallback(struct gpibMessage *gpibMessage) +{ + int timeout; + int nrequest, nactual; + + timeout = gpibMessage->timeout * sysClkRateGet() / 1000; + if (timeout < 1) timeout = 1; + + switch(gpibMessage->function) + { + case GPIB_SEND: + Debug(2, "gpibIOCallback, GPIB_SEND, message=%s\n", + gpibMessage->buffer); + /* write the message to the GPIB listen address */ + nrequest = gpibMessage->buff_len; + nactual =(*(drvGpib.writeIb))(gpibMessage->head.pibLink, + gpibMessage->address, gpibMessage->buffer, + nrequest, timeout); + if (nactual != nrequest) { + /* Something is wrong if we couldn't write everything */ + Debug(1, "Error writing GPIB, requested length=%d\n", nrequest); + Debug(1, " actual length=%d\n", nactual); + } + gpibMessage->status = nactual; + semGive(gpibMessage->semID); /* gpibIOSend is waiting for this */ + break; + case GPIB_RECEIVE: + /* Read a message from the GPIB listen address */ + nrequest = gpibMessage->buff_len; + nactual = (*(drvGpib.readIbEos))(gpibMessage->head.pibLink, + gpibMessage->address, gpibMessage->buffer, + nrequest, timeout, gpibMessage->terminator); + /* Append a null terminator if there is room */ + if (nactual < nrequest) gpibMessage->buffer[nactual]='\0'; + Debug(2, "gpibIOCallback, GPIB_RECEIVE, message=%s\n", + gpibMessage->buffer); + Debug(2, "gpibIOCallback, GPIB_RECEIVE, nrequest=%d\n", nrequest); + Debug(2, "gpibIOCallback, GPIB_RECEIVE, nactual=%d\n", nactual); + gpibMessage->status = nactual; + semGive(gpibMessage->semID); /* gpibIORecv is waiting for this */ + break; + } + return (0); +} diff --git a/motorApp/NewportSrc/gpibIO.h b/motorApp/NewportSrc/gpibIO.h new file mode 100644 index 00000000..fdcd2f4e --- /dev/null +++ b/motorApp/NewportSrc/gpibIO.h @@ -0,0 +1,15 @@ +#include +#include +#include +#include + +struct gpibInfo +{ + struct dpvtGpibHead head; + int address; + SEM_ID semID; +}; +struct gpibInfo *gpibIOInit(int link, int address); +int gpibIOSend(struct gpibInfo *info, char const *buffer, int buff_len, int timeout); +int gpibIORecv(struct gpibInfo *info, char *buffer, int buff_len, + int terminator, int timeout); diff --git a/motorApp/NewportSrc/serialIO.cc b/motorApp/NewportSrc/serialIO.cc new file mode 100644 index 00000000..7787eb8c --- /dev/null +++ b/motorApp/NewportSrc/serialIO.cc @@ -0,0 +1,148 @@ +/* + * These routines provide a simple interface to Hideos serial I/O. They hide + * the details of Hideos. They are intended for use by applications which do + * simple synchronous reads and writes, and no control operations like setting + * baud rates, etc. + */ + +#include "serialIO.h" + +#ifdef DEBUG +#define Debug(l, f, args...) {if (l <= serialIODebug) printf(f, ## args);} +#else +#define Debug(l, f, args...) +#endif + +int serialIODebug = 0; + + +struct serialInfo *cc_serialIOInit(int card, char *taskName) +{ + struct serialInfo *serialInfo; + int status; + + serialInfo = (struct serialInfo *) malloc(sizeof(struct serialInfo)); + serialInfo->bpd = new BPD; + status = serialInfo->bpd->Bind(serialInfo->td, card, taskName); + if (status < 0) { + Debug(1, + "serialIOInit: Cannot bind to remote Hideos task %s\n", taskName); + return (NULL); + } + else + Debug(1, + "serialIOInit: Bound to remote Hideos task %s\n", taskName); + return (serialInfo); +} + +int cc_serialIOSend(struct serialInfo *serialInfo, char const *buffer, + int buffer_len, int timeout) +{ + int status; + IndirectStringMsg* sm = (IndirectStringMsg*) + hideos_resources->GetMessagePool()->GetMessage(IndirectStringMsgType); + Message* rm; + + + if (sm->IsQueued()) { + printf("serialIOSend: 1st message %lx already queued!!\n", (long)sm); + exit(1); + } + + sm->SetWrite(timeout); + sm->SetBuffers((unsigned char *)buffer,buffer_len,NULL,0); + + status = serialInfo->bpd->Send(serialInfo->td, sm); + if (status != 0) { + Debug(1, "serialIOSend: error sending message %s\n", buffer); + hideos_resources->GetMessagePool()->FreeMessage(sm); + goto done; + } + Debug(2, "serialIOSend: sent message %s\n", buffer); + status = serialInfo->bpd->Receive((Message *&)rm); + if (status != 0) { + Debug(1, "serialIOSend: error receiving message, status=%d\n", status); + goto done; + } + Debug(2, "serialIOSend: received message, status=%d\n", status); + + hideos_resources->GetMessagePool()->FreeMessage(rm); + +done: + return (status); +} + + +int cc_serialIORecv(struct serialInfo *serialInfo, char *buffer, + int buffer_len, int terminator, int timeout) +{ + int status, nrec = 0; + IndirectStringMsg* sm = (IndirectStringMsg*) + hideos_resources->GetMessagePool()->GetMessage(IndirectStringMsgType); + IndirectStringMsg* rm; + + if (sm->IsQueued()) { + printf("serialIORecv: message %lx already queued!!\n", (long)sm); + exit(1); + } + + sm->SetRead(timeout); + if (terminator == -1) + sm->SetComplexDelimiter((char *)NULL, (char *)NULL, 0); + else + sm->SetSimpleDelimiter(terminator); + + sm->SetBuffers(NULL, 0, (unsigned char *)buffer, buffer_len); + + status = serialInfo->bpd->Send(serialInfo->td, sm); + if (status != 0) { + Debug(1, "serialIORecv: error sending message, status = %d\n", status); + hideos_resources->GetMessagePool()->FreeMessage(sm); + goto doneRecv; + } + Debug(2, "serialIORecv: sent message status = %d\n", status); + status = serialInfo->bpd->Receive((Message *&)rm); + if (status < 0) { + Debug(1,"serialIORecv: error receiving message, status = %d\n", + status); + goto doneRecv; + } + + nrec = sm->GetDestSize(); + if (sm->return_code != 0) { + Debug(1,"serialIORecv: error, return code = %ld\n", sm->return_code); + } + Debug(2,"serialIORecv: Received %d bytes\n", nrec); + /* Append a NULL byte to the response if there is room */ + if (nrec < buffer_len) buffer[nrec] = '\0'; + + Debug(2,"serialIORecv: Received message = %s\n", buffer); + + hideos_resources->GetMessagePool()->FreeMessage(rm); + +doneRecv: + return (nrec); +} + + +extern "C" +{ +struct serialInfo *serialIOInit(int card, char *taskName) +{ + return (cc_serialIOInit(card, taskName)); +} + +int serialIOSend(struct serialInfo *serialInfo, char const *buffer, + int buffer_len, int timeout) +{ + return (cc_serialIOSend(serialInfo, buffer, buffer_len, timeout)); +} + +int serialIORecv(struct serialInfo *serialInfo, char *buffer, int buffer_len, + int terminator, int timeout) +{ + return (cc_serialIORecv(serialInfo, buffer, buffer_len, + terminator, timeout)); +} + +} diff --git a/motorApp/NewportSrc/serialIO.h b/motorApp/NewportSrc/serialIO.h new file mode 100644 index 00000000..c86b9779 --- /dev/null +++ b/motorApp/NewportSrc/serialIO.h @@ -0,0 +1,51 @@ +#ifdef __cplusplus + +#include "vxWorks.h" +#include "vme.h" +#include "iv.h" +#include "stdio.h" +#include "string.h" +#include "cacheLib.h" +#include "taskLib.h" + +#include "gen/all_msg_ids.h" +#include "msg/serial_config_msg.h" +#include "msg/string_msg.h" +#include "hideos/globals.h" +#include "hideos/resources.h" +#include "hideos/msgpool.h" +#include "hideos/registry.h" +#include "hideos/drvBp.h" + + +struct serialInfo +{ + BPD *bpd; + TD td; +}; + +/* Function prototypes */ +struct serialInfo *cc_serialIOInit(int card, char *task); +int cc_serialIOSend(struct serialInfo *serialInfo, char const *buffer, + int buffer_len, int timeout); + +#else /* For C just define serialInfo as a dummy structure since it can't + understand the include files which define what it really is */ +struct serialInfo +{ + int dummy; +}; +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif +struct serialInfo *serialIOInit(int card, char *task); +int serialIOSend(struct serialInfo *serialInfo, char const *buffer, + int buffer_len, int timeout); +int serialIORecv(struct serialInfo *serialInfo, char *buffer, int buffer_len, + int terminator, int timeout); +#ifdef __cplusplus +} +#endif diff --git a/motorApp/OmsSrc/Makefile b/motorApp/OmsSrc/Makefile new file mode 100644 index 00000000..0380f4bf --- /dev/null +++ b/motorApp/OmsSrc/Makefile @@ -0,0 +1,3 @@ +TOP=../.. +include $(TOP)/config/CONFIG_APP +include $(TOP)/config/RULES_ARCHS diff --git a/motorApp/OmsSrc/Makefile.Host b/motorApp/OmsSrc/Makefile.Host new file mode 100644 index 00000000..2c4782db --- /dev/null +++ b/motorApp/OmsSrc/Makefile.Host @@ -0,0 +1,11 @@ +# Makefile.Host +TOP = ../../.. +include $(TOP)/config/CONFIG_APP +#---------------------------------------- +# ADD MACRO DEFINITIONS AFTER THIS LINE + +DBDINSTALL += devOms.dbd + +include $(TOP)/config/RULES.Host +#---------------------------------------- +# ADD RULES AFTER THIS LINE diff --git a/motorApp/OmsSrc/Makefile.Vx b/motorApp/OmsSrc/Makefile.Vx new file mode 100644 index 00000000..fd8e2f9e --- /dev/null +++ b/motorApp/OmsSrc/Makefile.Vx @@ -0,0 +1,30 @@ +# Makefile.Vx +TOP = ../../.. +include $(TOP)/config/CONFIG_APP +#---------------------------------------- +# ADD MACRO DEFINITIONS AFTER THIS LINE + +# The following are used for debugging messages. +#USR_CFLAGS += -DDEBUG +#USR_CXXFLAGS += -DDEBUG + +# The following is required for all OMS device drivers. +SRCS.c += ../devOmsCom.c +LIBOBJS += devOmsCom.o + +# The following is required for the OMS VME8/44 (i.e., devOMS) device driver. +SRCS.c += ../devOms.c ../drvOms.c +LIBOBJS += devOms.o drvOms.o + +# The following is required for the OMS VME58 (i.e., devOms58) device driver. +SRCS.c += ../devOms58.c ../drvOms58.c +LIBOBJS += devOms58.o drvOms58.o + +LIBNAME = OmsLib + +#Note that the command line that builds the +#library $(LIBNAME) may be HUGE (>3kB) +# +include $(TOP)/config/RULES.Vx +#---------------------------------------- +# ADD RULES AFTER THIS LINE diff --git a/motorApp/OmsSrc/devOms.c b/motorApp/OmsSrc/devOms.c new file mode 100644 index 00000000..db628f9a --- /dev/null +++ b/motorApp/OmsSrc/devOms.c @@ -0,0 +1,132 @@ +/* +FILENAME... devOms.c +USAGE... Device level support for OMS VME8 and VME44 models. + +Version: $Revision: 1.1 $ +Modified By: $Author: sluiter $ +Last Modified: $Date: 2000-02-08 22:19:00 $ +*/ + +/* + * Original Author: Jim Kowalkowski + * Date: 01/18/93 + * Current Author: Ron Sluiter + * + * Experimental Physics and Industrial Control System (EPICS) + * + * Copyright 1991, the Regents of the University of California, + * and the University of Chicago Board of Governors. + * + * This software was produced under U.S. Government contracts: + * (W-7405-ENG-36) at the Los Alamos National Laboratory, + * and (W-31-109-ENG-38) at Argonne National Laboratory. + * + * Initial development by: + * The Controls and Automation Group (AT-8) + * Ground Test Accelerator + * Accelerator Technology Division + * Los Alamos National Laboratory + * + * Co-developed with + * The Controls and Computing Group + * Accelerator Systems Division + * Advanced Photon Source + * Argonne National Laboratory + * + * Modification Log: + * ----------------- + * .01 01-18-93 jbk initialized + * ... + * .03 03-19-96 tmm v1.10: modified encoder-ratio calculation + * .04 11-26-96 jps allow for bumpless-reboot on position + * .04a 02-19-97 tmm fixed for EPICS 3.13 + */ + + +#include +#include /* jps: include for init_record wait */ +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef __cplusplus +extern "C" { +#include +#include +} +#else +#include +#include +#endif + +#include "motorRecord.h" +#include "motor.h" +#include "drvOms.h" +#include "devOmsCom.h" + +#define STATIC static + +extern int oms44_num_cards; +extern struct driver_table oms_access; + +/* ----------------Create the dsets for devOMS----------------- */ +STATIC long oms_init(int); +STATIC long oms_init_record(struct motorRecord *); +STATIC long oms_start_trans(struct motorRecord *); +STATIC long oms_end_trans(struct motorRecord *); + +struct motor_dset devOMS = +{ + {8, NULL, oms_init, oms_init_record, NULL}, + motor_update_values, + oms_start_trans, + oms_build_trans, + oms_end_trans +}; + +STATIC struct board_stat **oms_cards; +STATIC const char errmsg[] = {"\n\n!!!ERROR!!! - Oms driver uninitialized.\n"}; + +STATIC long oms_init(int after) +{ + if (*(oms_access.init_indicator) == NO) + { + errlogSevPrintf(errlogMinor, "%s", errmsg); + return(ERROR); + } + else + return(motor_init_com(after, oms44_num_cards, &oms_access, &oms_cards)); +} + +STATIC long oms_init_record(struct motorRecord *mr) +{ + return(motor_init_record_com(mr, oms44_num_cards, &oms_access, oms_cards)); +} + +STATIC long oms_start_trans(struct motorRecord *mr) +{ + struct motor_trans *trans; + long rtnval; + + rtnval = motor_start_trans_com(mr, oms_cards, ADDRESS); + /* Initialize a STOP_AXIS command termination string pointer. */ + trans = (struct motor_trans *) mr->dpvt; + trans->motor_call.termstring = " ID"; + return(rtnval); +} + +STATIC long oms_end_trans(struct motorRecord *mr) +{ + if (*(oms_access.init_indicator) == NO) + { + errlogSevPrintf(errlogMinor, "%s", errmsg); + return(ERROR); + } + else + return(motor_end_trans_com(mr, &oms_access, "\r")); +} + diff --git a/motorApp/OmsSrc/devOms.dbd b/motorApp/OmsSrc/devOms.dbd new file mode 100644 index 00000000..311599f7 --- /dev/null +++ b/motorApp/OmsSrc/devOms.dbd @@ -0,0 +1,6 @@ +# Oregon Micro Systems VME8/44 and VME58 Driver support. +device(motor,VME_IO,devOMS,"OMS VME8/44") +device(motor,VME_IO,devOms58,"OMS VME58") +driver(drvOms) +driver(drvOms58) + diff --git a/motorApp/OmsSrc/devOms58.c b/motorApp/OmsSrc/devOms58.c new file mode 100644 index 00000000..5524f819 --- /dev/null +++ b/motorApp/OmsSrc/devOms58.c @@ -0,0 +1,136 @@ +/* +FILENAME... devOms58.c +USAGE... Motor record device level support for OMS VME58. + +Version: $Revision: 1.1 $ +Modified By: $Author: sluiter $ +Last Modified: $Date: 2000-02-08 22:19:01 $ +*/ + +/* + * Original Author: Jim Kowalkowski + * Current Author: Joe Sullivan + * Date: 11/14/94 + * + * Experimental Physics and Industrial Control System (EPICS) + * + * Copyright 1991, the Regents of the University of California, + * and the University of Chicago Board of Governors. + * + * This software was produced under U.S. Government contracts: + * (W-7405-ENG-36) at the Los Alamos National Laboratory, + * and (W-31-109-ENG-38) at Argonne National Laboratory. + * + * Initial development by: + * The Controls and Automation Group (AT-8) + * Ground Test Accelerator + * Accelerator Technology Division + * Los Alamos National Laboratory + * + * Co-developed with + * The Controls and Computing Group + * Accelerator Systems Division + * Advanced Photon Source + * Argonne National Laboratory + * + * Modification Log: + * ----------------- + * .01 01-18-93 jbk initialized + * .02 11-14-94 jps copy devOMS.c and modify to point to vme58 driver + * .03 03-19-96 tmm v1.10: modified encoder-ratio calculation + * .04 06-20-96 jps allow for bumpless-reboot on position + * .04a 02-19-97 tmm fixed for EPICS 3.13 + * ... + */ + + +#include +#include /* jps: include for init_record wait */ +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef __cplusplus +extern "C" { +#include +#include +} +#else +#include +#include +#endif + +#include "motorRecord.h" +#include "motor.h" +#include "drvOms58.h" +#include "devOmsCom.h" + +#define STATIC static + +extern int oms58_num_cards; +extern struct driver_table oms58_access; + +/* ----------------Create the dsets for devOMS----------------- */ +STATIC long oms_init(int after); +STATIC long oms_init_record(struct motorRecord *); +STATIC long oms_start_trans(struct motorRecord *); +STATIC long oms_end_trans(struct motorRecord *); + +struct motor_dset devOms58 = +{ + {8, NULL, oms_init, oms_init_record, NULL}, + motor_update_values, + oms_start_trans, + oms_build_trans, + oms_end_trans +}; + + +STATIC struct board_stat **oms_cards; +STATIC const char errmsg[] = {"\n\n!!!ERROR!!! - Oms58 driver uninitialized.\n"}; + +STATIC long oms_init(int after) +{ + if (*(oms58_access.init_indicator) == NO) + { + errlogSevPrintf(errlogMinor, "%s", errmsg); + return(ERROR); + } + else + return(motor_init_com(after, oms58_num_cards, &oms58_access, &oms_cards)); +} + +STATIC long oms_init_record(struct motorRecord *mr) +{ + return(motor_init_record_com(mr, oms58_num_cards, &oms58_access, oms_cards)); +} + +STATIC long oms_start_trans(struct motorRecord *mr) +{ + struct motor_trans *trans; + long rtnval; + + rtnval = motor_start_trans_com(mr, oms_cards, ADDRESS); + /* Initialize a STOP_AXIS command termination string pointer. */ + trans = (struct motor_trans *) mr->dpvt; + trans->motor_call.termstring = " ID"; + return(rtnval); +} + +STATIC long oms_end_trans(struct motorRecord *mr) +{ + if (*(oms58_access.init_indicator) == NO) + { + errlogSevPrintf(errlogMinor, "%s", errmsg); + return(ERROR); + } + else + return(motor_end_trans_com(mr, &oms58_access, "\r")); +} + + + diff --git a/motorApp/OmsSrc/devOmsCom.c b/motorApp/OmsSrc/devOmsCom.c new file mode 100644 index 00000000..cabc015f --- /dev/null +++ b/motorApp/OmsSrc/devOmsCom.c @@ -0,0 +1,220 @@ +/* +FILENAME... devOmsCom.c +USAGE... Data and functions common to all OMS device level support. + +Version: $Revision: 1.1 $ +Modified By: $Author: sluiter $ +Last Modified: $Date: 2000-02-08 22:19:01 $ +*/ + +/* + * Original Author: Jim Kowalkowski + * Current Author: Joe Sullivan + * Date: 11/14/94 + * + * Experimental Physics and Industrial Control System (EPICS) + * + * Copyright 1991, the Regents of the University of California, + * and the University of Chicago Board of Governors. + * + * This software was produced under U.S. Government contracts: + * (W-7405-ENG-36) at the Los Alamos National Laboratory, + * and (W-31-109-ENG-38) at Argonne National Laboratory. + * + * Initial development by: + * The Controls and Automation Group (AT-8) + * Ground Test Accelerator + * Accelerator Technology Division + * Los Alamos National Laboratory + * + * Co-developed with + * The Controls and Computing Group + * Accelerator Systems Division + * Advanced Photon Source + * Argonne National Laboratory + * + * Modification Log: + * ----------------- + * .01 01-18-93 jbk initialized + * .02 11-14-94 jps copy devOMS.c and modify to point to vme58 driver + * .03 03-19-96 tmm v1.10: modified encoder-ratio calculation + * .04 06-20-96 jps allow for bumpless-reboot on position + * .04a 02-19-97 tmm fixed for EPICS 3.13 + */ + +#include +#include +#include +#include + +#include "motorRecord.h" +#include "motor.h" +#include "motordevCom.h" + +/* Command set used by record support. WARNING! this must match + "motor_cmnds" in motor.h . +*/ + + +struct motor_table +{ + unsigned char type; + const char *command; + int num_parms; +}; + +struct motor_table const oms_table[] = +{ + {MOTION, " MA", 1}, /* MOVE_ABS */ + {MOTION, " MR", 1}, /* MOVE_REL */ + {MOTION, " HM", 1}, /* HOME_FOR */ + {MOTION, " HR", 1}, /* HOME_REV */ + {IMMEDIATE, " LP", 1}, /* LOAD_POS */ + {IMMEDIATE, " VB", 1}, /* SET_VELO_BASE */ + {IMMEDIATE, " VL", 1}, /* SET_VELO */ + {IMMEDIATE, " AC", 1}, /* SET_ACCEL */ + {IMMEDIATE, " GD", 0}, /* jps: from GO to GD */ + {IMMEDIATE, " ER", 2}, /* SET_ENC_RATIO */ + {INFO, " ", 0}, /* GET_INFO */ + {MOVE_TERM, " ST", 0}, /* STOP_AXIS */ + {VELOCITY, " JG", 1}, /* JOG */ + {IMMEDIATE," KP", 1}, /* SET_PGAIN */ + {IMMEDIATE," KI", 1}, /* SET_IGAIN */ + {IMMEDIATE," KD", 1}, /* SET_DGAIN */ + {IMMEDIATE," HN", 0}, /* ENABLE_TORQUE */ + {IMMEDIATE," HF", 0}, /* DISABL_TORQUE */ + {IMMEDIATE, "", 0}, /* PRIMITIVE */ + {IMMEDIATE, "", 0}, /* SET_HIGH_LIMIT */ + {IMMEDIATE, "", 0}, /* SET_LOW_LIMIT */ +}; + + +/* add a part to the transaction */ +long oms_build_trans(motor_cmnd command, double *parms, struct motorRecord *mr) +{ + struct motor_trans *trans = (struct motor_trans *) mr->dpvt; + struct mess_node *motor_call; + char buffer[40]; + unsigned char cmnd_type; + long rtnind; + + rtnind = OK; + motor_call = &trans->motor_call; + + cmnd_type = oms_table[command].type; + if (cmnd_type > motor_call->type) + motor_call->type = cmnd_type; + + /* concatenate onto the dpvt message field */ + if (trans->state == BUILD_STATE) + { + if ((command == PRIMITIVE) && (mr->init != NULL) && + (strlen(mr->init) != 0)) + { + extern struct driver_table oms58_access; + /* Test for a "device directive" in the Initialization string. */ + if ((mr->init[0] == '@') && (trans->tabptr == &oms58_access)) + { + char *end = strrchr(&mr->init[1], '@'); + if (end != NULL) + { + struct driver_table *tabptr = trans->tabptr; + int size = (end - &mr->init[0]) + 1; + strncpy(buffer, mr->init, size); + buffer[size] = NULL; + if (strcmp(buffer, "@DPM_ON@") == 0) + { + int response, bitselect; + char respbuf[10]; + + (*tabptr->getmsg)(motor_call->card, respbuf, -1); + (*tabptr->sendmsg)(motor_call->card, "RB\r", + (char) NULL); + (*tabptr->getmsg)(motor_call->card, respbuf, 1); + if (sscanf(respbuf, "%x", &response) == 0) + response = 0; /* Force an error. */ + bitselect = (1 << motor_call->signal); + if ((response & bitselect) == 0) + trans->dpm = ON; + else + logMsg((char *) "Invalid VME58 configuration; RB = 0x%x\n", + response, 0, 0, 0, 0 ,0); + } + end++; + strcpy(buffer, end); + } + else + strcpy(buffer, mr->init); + } + else + strcpy(buffer, mr->init); + + strcat(motor_call->message, " "); + strcat(motor_call->message, buffer); + } + else + { + int first_one, itera; + + if (command == SET_HIGH_LIMIT || command == SET_LOW_LIMIT) + trans->state = IDLE_STATE; /* No command sent to the controller. */ + else + { + /* Silently enforce minimum range on KP command. */ + if (command == SET_PGAIN && *parms < 0.00005) + { + *parms = 0.00005; + mr->pcof = 0.00005; + rtnind = ERROR; + } + + if (cmnd_type == MOTION || cmnd_type == VELOCITY) + { + if (strlen(mr->prem) != 0) + { + strcat(motor_call->message, mr->prem); + strcat(motor_call->message, " "); + } + if (strlen(mr->post) != 0) + motor_call->postmsgptr = (char *) &mr->post; + } + /* put in command */ + strcat(motor_call->message, oms_table[command].command); + + /* put in parameters */ + for (first_one = YES, itera = 0; itera < oms_table[command].num_parms; + itera++) + { + if (first_one == YES) + first_one = NO; + else + strcat(motor_call->message, ","); + + if (command == SET_PGAIN || command == SET_IGAIN || + command == SET_DGAIN) + { + *parms *= 1999.9; + sprintf(buffer, "%.1f", parms[itera]); + } + else if (command == SET_VELOCITY) /* OMS errors if VB = VL. */ + { + long vbase = NINT(mr->vbas / fabs(mr->res)); + long vel = NINT(parms[itera]); + + if (vel <= vbase) + vel = vbase + 1; + sprintf(buffer, "%ld", vel); + } + else + sprintf(buffer, "%ld", NINT(parms[itera])); + strcat(motor_call->message, buffer); + } + } + } + } + else + rtnind = ERROR; + return(rtnind); +} + + diff --git a/motorApp/OmsSrc/devOmsCom.h b/motorApp/OmsSrc/devOmsCom.h new file mode 100644 index 00000000..de0b1ea2 --- /dev/null +++ b/motorApp/OmsSrc/devOmsCom.h @@ -0,0 +1,41 @@ +/* +FILENAME.. devOmsCom.h + +USAGE... This file contains OMS device information that is + common to all OMS device support modules. + +Version: $Revision: 1.1 $ +Modified By: $Author: sluiter $ +Last Modified: $Date: 2000-02-08 22:19:01 $ +*/ + +/* + * Original Author: Ron Sluiter + * Date: 12/22/98 + * + * Experimental Physics and Industrial Control System (EPICS) + * + * Copyright 1991, the University of Chicago Board of Governors. + * + * This software was produced under U.S. Government contract + * W-31-109-ENG-38 at Argonne National Laboratory. + * + * Beamline Controls & Data Acquisition Group + * Experimental Facilities Division + * Advanced Photon Source + * Argonne National Laboratory + * + * Modification Log: + * ----------------- + */ + +#ifndef INCdevOmsComh +#define INCdevOmsComh 1 + +#include "motordevCom.h" + +#define ADDRESS "A?" + +extern long oms_build_trans(motor_cmnd, double *, struct motorRecord *); + +#endif /* INCdevOmsComh */ diff --git a/motorApp/OmsSrc/drvOms.c b/motorApp/OmsSrc/drvOms.c new file mode 100644 index 00000000..97ad16fc --- /dev/null +++ b/motorApp/OmsSrc/drvOms.c @@ -0,0 +1,1126 @@ +/* +FILENAME... drvOms.c +USAGE... Driver level support for OMS models VME8 and VME44. + +Version: $Revision: 1.1 $ +Modified By: $Author: sluiter $ +Last Modified: $Date: 2000-02-08 22:19:01 $ +*/ + +/* + * Experimental Physics and Industrial Control System (EPICS) + * + * Copyright 1991, the Regents of the University of California, + * and the University of Chicago Board of Governors. + * + * This software was produced under U.S. Government contracts: + * (W-7405-ENG-36) at the Los Alamos National Laboratory, + * and (W-31-109-ENG-38) at Argonne National Laboratory. + * + * Initial development by: + * The Controls and Automation Group (AT-8) + * Ground Test Accelerator + * Accelerator Technology Division + * Los Alamos National Laboratory + * + * Co-developed with + * The Controls and Computing Group + * Accelerator Systems Division + * Advanced Photon Source + * Argonne National Laboratory + * + * Modification Log: + * ----------------- + */ + +/*========================stepper motor driver ======================== + + function: + Allow users to queue messages to axis on a OMS stepper + motor controller board. Each axis of every board available can + be accessed independantly. + + public functions: + motor_init() - Initialize the driver task and structures for all + boards available for the system. + private functions: + send_mess() - Send a message to the OMS board. + recv_mess() - Receive a message from the OMS board. + + +========================stepper motor driver ========================*/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#ifdef __cplusplus +extern "C" { +#include +#include +#include +} +#else +#include +#include +#include +#endif +#include +#include + +#include "motor.h" +#include "drvOms.h" + +#define PRIVATE_FUNCTIONS 1 /* normal:1, debug:0 */ +#define STATIC static + +/* Define for return test on locationProbe() */ +#define PROBE_SUCCESS(STATUS) ((STATUS)==S_dev_addressOverlap) + +#define CMD_CLEAR '\030' /* Control-X, clears command errors only */ + +#define ALL_INFO "A? QA RP RE EA\n" /* jps: move QA to top. */ +#define AXIS_INFO "A? QA RP\n" /* jps: move QA to top. */ +#define ENCODER_QUERY "A? EA\n" +#define DONE_QUERY "A? RA\n" + + +/*----------------debugging-----------------*/ +#ifdef DEBUG + #define Debug(l, f, args...) { if(l<=drvOMSdebug) printf(f,## args); } +#else + #define Debug(l, f, args...) +#endif + +/* Global data. */ +int oms44_num_cards = 0; +volatile int drvOMSdebug = 0; +volatile int omsSpyClkRate = 0; +#ifdef __cplusplus +extern "C" long locationProbe(epicsAddressType, char *); +#else +extern long locationProbe(epicsAddressType, char *); +#endif + +/* Local data required for every driver; see "motordrvComCode.h" */ +#include "motordrvComCode.h" + +/* --- Local data common to all OMS drivers. --- */ +STATIC char *oms_addrs = 0x0; +STATIC volatile unsigned omsInterruptVector = 0; +STATIC volatile uint8_t omsInterruptLevel = OMS_INT_LEVEL; +STATIC volatile int max_io_tries = MAX_COUNT; +STATIC volatile int motionTO = 10; + +/*----------------functions-----------------*/ + +/* Common local function declarations. */ +static long report(int level); +static long init(); +static void query_done(int, int, struct mess_node *); +static int set_status(int card, int signal); +static int send_mess(int card, char const *com, char c); +static int recv_mess(int, char *, int); +static void motorIsr(int card); +static int motor_init(); +static void oms_reset(); + +STATIC int omsGet(int card, char *pcom, int timeout); +STATIC int omsPut(int card, char *pcom); +STATIC int omsError(int card); +STATIC int motorIsrEnable(int card); +STATIC void motorIsrDisable(int card); + +/*----------------functions-----------------*/ + +struct driver_table oms_access = +{ + NULL, + motor_send, + motor_free, + motor_card_info, + motor_axis_info, + &mess_queue, + &queue_lock, + &free_list, + &freelist_lock, + &motor_sem, + &motor_state, + &total_cards, + &any_motor_in_motion, + send_mess, + recv_mess, + set_status, + query_done, + NULL, + &initialized +}; + +struct +{ + long number; +#ifdef __cplusplus + long (*report) (int); + long (*init) (void); +#else + DRVSUPFUN report; + DRVSUPFUN init; +#endif +} drvOms = {2, report, init}; + +static long report(int level) +{ + int card; + + if (oms44_num_cards <= 0) + printf(" No VME8/44 controllers configured.\n"); + else + { + for (card = 0; card < oms44_num_cards; card++) + if (motor_state[card]) + printf(" Oms VME8/44 motor card %d @ 0x%X, id: %s \n", card, + (uint_t) motor_state[card]->localaddr, + motor_state[card]->ident); + } + return (0); +} + +static long init() +{ + initialized = ON; /* Indicate that driver is initialized. */ + (void) motor_init(); + return ((long) 0); +} + + +STATIC void query_done(int card, int axis, struct mess_node *nodeptr) +{ + char buffer[40]; + + send_mess(card, DONE_QUERY, oms_trans_axis[axis]); + recv_mess(card, buffer, 1); + + if (nodeptr->status & RA_PROBLEM) + send_mess(card, AXIS_STOP, oms_trans_axis[axis]); +} + + +STATIC int set_status(int card, int signal) +{ + struct mess_info *motor_info; + char *p, *tok_save; + struct axis_status *ax_stat; + struct encoder_status *en_stat; + char q_buf[50], outbuf[50]; + int i, pos; + int rtn_state; + + motor_info = &(motor_state[card]->motor_info[signal]); + + if (motor_state[card]->motor_info[signal].encoder_present == YES) + { + /* get 4 peices of info from axis */ + send_mess(card, ALL_INFO, oms_trans_axis[signal]); + recv_mess(card, q_buf, 4); + } + else + { + send_mess(card, AXIS_INFO, oms_trans_axis[signal]); + recv_mess(card, q_buf, 2); + } + + Debug(5, "info = (%s)\n", q_buf); + + for (i = 0, p = strtok_r(q_buf, ",", &tok_save); p; + p = strtok_r(NULL, ",", &tok_save), i++) + { + switch (i) + { + case 0: /* axis status */ + ax_stat = (struct axis_status *) p; + + if (ax_stat->direction == 'P') + motor_info->status |= RA_DIRECTION; + else + motor_info->status &= ~RA_DIRECTION; + + if (ax_stat->done == 'D') + motor_info->status |= RA_DONE; + else + motor_info->status &= ~RA_DONE; + + if (ax_stat->overtravel == 'L') + motor_info->status |= RA_OVERTRAVEL; + else + motor_info->status &= ~RA_OVERTRAVEL; + + if (ax_stat->home == 'H') + motor_info->status |= RA_HOME; + else + motor_info->status &= ~RA_HOME; + + break; + case 1: /* motor pulse count (position) */ + sscanf(p, "%i", &pos); + + if (pos == motor_info->position) + motor_info->no_motion_count++; + else + motor_info->no_motion_count = 0; + + if (motor_info->no_motion_count > motionTO) + { + motor_info->status |= RA_PROBLEM; + send_mess(card, AXIS_STOP, oms_trans_axis[signal]); + motor_info->no_motion_count = 0; + errlogSevPrintf(errlogMinor, "Motor motion timeout ERROR on card: %d, signal: %d\n", + card, signal); + } + else + motor_info->status &= ~RA_PROBLEM; + + motor_info->position = pos; + break; + case 2: /* encoder pulse count (position) */ + { + int temp; + + sscanf(p, "%i", &temp); + motor_info->encoder_position = (int32_t) temp; + } + break; + case 3: /* encoder status */ + en_stat = (struct encoder_status *) p; + + if (en_stat->slip_enable == 'E') + motor_info->status |= EA_SLIP; + else + motor_info->status &= ~EA_SLIP; + + if (en_stat->pos_enable == 'E') + motor_info->status |= EA_POSITION; + else + motor_info->status &= ~EA_POSITION; + + if (en_stat->slip_detect == 'S') + motor_info->status |= EA_SLIP_STALL; + else + motor_info->status &= ~EA_SLIP_STALL; + + if (en_stat->axis_home == 'H') + motor_info->status |= EA_HOME; + else + motor_info->status &= ~EA_HOME; + + break; + default: + break; + } + } + + /* + * jps: Velocity should be set based on the actual velocity returned from + * the 'RV' command (See drvOms58.c). But the polling task does not have + * time to request additional information so the velocity is set to + * indicate moving or not-moving. + */ + if (motor_info->status & RA_DONE) + motor_info->velocity = 0; + else + motor_info->velocity = 1; + + if (!(motor_info->status & RA_DIRECTION)) + motor_info->velocity *= -1; + + rtn_state = (!motor_info->no_motion_count || + (motor_info->status & (RA_OVERTRAVEL | RA_DONE | RA_PROBLEM))) ? 1 : 0; + + /* Test for post-move string. */ + if ((motor_info->status & RA_DONE || motor_info->status & RA_OVERTRAVEL) && + (motor_info->motor_motion != 0) && + (motor_info->motor_motion->postmsgptr != 0)) + { + strcpy(outbuf, "A? "); + strcat(outbuf, motor_info->motor_motion->postmsgptr); + strcat(outbuf, "\n"); + send_mess(card, outbuf, oms_trans_axis[signal]); + motor_info->motor_motion->postmsgptr = NULL; + } + + return (rtn_state); +} + + +/*****************************************************/ +/* send a message to the OMS board */ +/* send_mess() */ +/*****************************************************/ +STATIC int send_mess(int card, char const *com, char inchar) +{ + char outbuf[MAX_MSG_SIZE]; + int return_code; + + if (strlen(com) > MAX_MSG_SIZE) + { + logMsg((char *) "drvOms.cc:send_mess(); message size violation.\n", + 0, 0, 0, 0, 0, 0); + return (-1); + } + + /* Check that card exists */ + if (!motor_state[card]) + { + logMsg((char *) "drvOms.cc:send_mess() - invalid card #%d\n", card, + 0, 0, 0, 0, 0); + return (-1); + } + + /* Check/Clear command errors */ + omsError(card); + + /* Flush receive buffer */ + recv_mess(card, (char *) NULL, -1); + + strcpy(outbuf, com); + + if (inchar != (char) NULL) + outbuf[1] = inchar; /* put in axis */ + + Debug(9, "send_mess: ready to send message.\n"); + + return_code = omsPut(card, outbuf); + + if (return_code == OK) + { + Debug(4, "sent message (%s)\n", outbuf); + } + else + { + Debug(4, "unable to send message (%s)\n", outbuf); + } + return (return_code); +} + + +/* + * FUNCTION... recv_mess(int card, char *com, int amount) + * + * INPUT ARGUMENTS... + * card - controller card # (0,1,...). + * *com - caller's response buffer. + * amount | -1 = flush controller's output buffer. + * | >= 1 = the # of command responses to retrieve into caller's + * response buffer. + * + * LOGIC... + * IF controller card does not exist. + * ERROR RETURN. + * ENDIF + * IF "amount" indicates buffer flush. + * WHILE characters left in input buffer. + * Call omsGet(). + * ENDWHILE + * ENDIF + * + * FOR each message requested (i.e. "amount"). + * Initialize head and tail pointers. + * Initialize retry counter and state indicator. + * WHILE retry count not exhausted, AND, state indicator is NOT at END. + * IF characters left in controller's input buffer. + * Process input character. + * ELSE IF command error occured - call omsError(). + * ERROR RETURN. + * ENDIF + * ENDWHILE + * IF retry count exhausted. + * Terminate receive buffer. + * ERROR RETURN. + * ENDIF + * Terminate command response. + * ENDFOR + * + * IF commands processed. + * Terminate response buffer. + * ELSE + * Clear response buffer. + * ENDIF + * NORMAL RETURN. + */ + +STATIC int recv_mess(int card, char *com, int amount) +{ + int i, trys; + char junk; + char inchar; + int piece, head_size, tail_size; + int return_code; + + return_code = 0; + inchar = '\0'; + + /* Check that card exists */ + if (card >= total_cards) + { + Debug(1, "recv_mess - invalid card #%d\n", card); + return (-1); + } + + if (amount == -1) + { + /* Process request to flush receive queue */ + Debug(7, "recv flush -------------"); + while (omsGet(card, &junk, 0)) + { + Debug(7, "%inchar", junk); + } + Debug(7, " -------------"); + return (0); + } + + for (i = 0; amount > 0; amount--) + { + Debug(7, "-------------"); + head_size = 0; + tail_size = 0; + + for (piece = 0, trys = 0; piece < 3 && trys < 3; trys++) + { + if (omsGet(card, &inchar, max_io_tries)) + { + Debug(7, "%02x", inchar); + + switch (piece) + { + case 0: /* header */ + if (inchar == '\n' || inchar == '\r') + head_size++; + else + { + piece++; + com[i++] = inchar; + } + break; + case 1: /* body */ + if (inchar == '\n' || inchar == '\r') + { + piece++; + tail_size++; + } + else + com[i++] = inchar; + break; + + case 2: /* trailer */ + tail_size++; + if (tail_size >= head_size) + piece++; + break; + } + + trys = 0; + } + else if (omsError(card)) + /* Command error detected - abort recv */ + return (-1); + } + Debug(7, "-------------\n"); + if (trys >= 3) + { + Debug(1, "Timeout occurred in recv_mess\n"); + com[i] = '\0'; + return (-1); + } + com[i++] = ','; + } + + if (i > 0) + com[i - 1] = '\0'; + else + com[i] = '\0'; + + Debug(4, "recv_mess: card %d", card); + Debug(4, " com %s\n", com); + return (0); +} + + +/*****************************************************/ +/* Get next character from OMS input buffer */ +/* omsGet() */ +/*****************************************************/ +STATIC int omsGet(int card, char *pchar, int timeout) +{ + volatile struct controller *pmotorState; + volatile struct vmex_motor *pmotor; + int getCnt = 0; + int retry = 0; + + pmotorState = motor_state[card]; + + if (pmotorState->irqdata->irqEnable) + { + /* Get character from isr - if available */ + while ((getCnt = rngBufGet(pmotorState->irqdata->recv_rng, pchar, 1)) == 0 && + retry < timeout) + { + semTake(pmotorState->irqdata->recv_sem, 1); /* Wait for character */ + retry += 5000; /* Compensate for semaphore timeout */ + } + } + else + { + /* Direct read from card */ + pmotor = (struct vmex_motor *) pmotorState->localaddr; + + while (getCnt == 0 && retry++ < timeout) + { + if (pmotor->status & STAT_INPUT_BUF_FULL) + { + getCnt++; + *pchar = pmotor->data; + } + } + } + return (getCnt); +} + +/*****************************************************/ +/* Send Message to OMS */ +/* omsPut() */ +/*****************************************************/ +STATIC int omsPut(int card, char *pmess) +{ + volatile struct controller *pmotorState; + volatile struct vmex_motor *pmotor; + int key; + char *p; + int putCnt; + int trys; + + pmotorState = motor_state[card]; + pmotor = (struct vmex_motor *) pmotorState->localaddr; + + /* + * This section enables the transmitt interrupt - it is not used because + * of driver-failure during worst-case testing. + */ + if (FALSE) + { + /* Put string into isr transmitt buffer */ + putCnt = strlen(pmess); + if (rngBufPut(pmotorState->irqdata->send_rng, pmess, putCnt) != putCnt) + { + Debug(1, "omsPut: Put ring full.\n"); + return (ERROR); + } + + /* Turn-on transmit buffer interrupt */ + key = intLock(); + pmotor->control |= IRQ_TRANS_BUF; + intUnlock(key); + + return (semTake(pmotorState->irqdata->send_sem, MAX_COUNT / 5000)); + } + else + { + /* Send next message */ + for (p = pmess; *p != '\0'; p++) + { + trys = 0; + while (!(pmotor->status & STAT_TRANS_BUF_EMPTY)) + { + if (trys > max_io_tries) + { + Debug(1, "omsPut: Time_out occurred in send\n"); + return (ERROR); + } + if (pmotor->status & STAT_ERROR) + { + Debug(1, "omsPut: error occurred in send\n"); + } + trys++; + } + pmotor->data = *p; + } + } + return (OK); +} + + +/*****************************************************/ +/* Clear OMS errors */ +/* omsClearErrors() */ +/*****************************************************/ +STATIC int omsError(int card) +{ + volatile struct controller *pmotorState; + volatile struct vmex_motor *pmotor; + int rtnStat = FALSE; + + pmotorState = motor_state[card]; + pmotor = (struct vmex_motor *) pmotorState->localaddr; + + if (pmotorState->irqdata->irqEnable) + { + /* Check status of last message */ + if (pmotorState->irqdata->irqErrno & STAT_ERROR) + { + /* Error on the card is cleared by the ISR */ + pmotorState->irqdata->irqErrno &= ~STAT_ERROR; + rtnStat = TRUE; + } + } + else + { + int i; + char const *p; + + /* Check/Clear command error from last message */ + if ((pmotor->status) & STAT_ERROR) + { + Debug(1, "omsPut: Error detected! 0x%02x\n", pmotor->status); + for (p = ERROR_CLEAR; *p != '\0'; p++) + { + while (!(pmotor->status & STAT_TRANS_BUF_EMPTY)); + pmotor->data = *p; + } + for (i = 0; i < 20000; i++); + rtnStat = TRUE; + } + } + return (rtnStat); +} + + +/*****************************************************/ +/* Interrupt service routine. */ +/* motorIsr() */ +/*****************************************************/ +STATIC void motorIsr(int card) +{ + volatile struct controller *pmotorState; + volatile struct vmex_motor *pmotor; + uint8_t control; + uint8_t status; + uint8_t doneFlags; + char dataChar; + + if (card >= total_cards || (pmotorState = motor_state[card]) == NULL) + { + logMsg((char *) "Invalid entry-card #%d\n", card, 0, 0, 0, 0, 0); + return; + } + + pmotor = (struct vmex_motor *) (pmotorState->localaddr); + + /* Save interrupt state */ + control = pmotor->control; + + /* Status register - clear irqs on read. */ + status = pmotor->status; + + /* Done register - clears on read */ + doneFlags = pmotor->done; + + /* Determine cause of entry */ + + if (drvOMSdebug >= 10) + logMsg((char *) "entry card #%d,status=0x%X,done=0x%X\n", card, + status, doneFlags, 0, 0, 0); + + /* Motion done handling */ + if (status & STAT_DONE) + /* Wake up polling task 'motor_task()' to issue callbacks */ + semGive(motor_sem); + + /* If command error is present - clear it */ + if (status & STAT_ERROR) + { + pmotor->data = (uint8_t) CMD_CLEAR; + + /* Send null character to indicate error */ + if (drvOMSdebug >= 1) + logMsg((char *) "command error detected on card %d\n", card, 0, 0, + 0, 0, 0); + + pmotorState->irqdata->irqErrno |= STAT_ERROR; + } + + /* Send message */ +/* if (status & STAT_TRANS_BUF_EMPTY) */ + if (FALSE) + { + if (rngBufGet(pmotorState->irqdata->send_rng, &dataChar, 1)) + pmotor->data = dataChar; + else + { + /* Transmit done - disable irq */ + semGive(pmotorState->irqdata->send_sem); + control &= ~IRQ_TRANS_BUF; + } + } + + /* Read Response */ + if (status & STAT_INPUT_BUF_FULL) + { + dataChar = pmotor->data; + + if (!rngBufPut(pmotorState->irqdata->recv_rng, &dataChar, 1)) + { + logMsg((char *) "card %d recv ring full, lost '%c'\n", card, + dataChar, 0, 0, 0, 0); + pmotorState->irqdata->irqErrno |= STAT_INPUT_BUF_FULL; + } + semGive(pmotorState->irqdata->recv_sem); + } + /* Update-interrupt state */ + pmotor->control = control; +} + +STATIC int motorIsrEnable(int card) +{ + volatile struct controller *pmotorState; + volatile struct vmex_motor *pmotor; + long status; + uint8_t cardStatus; + + Debug(5, "motorIsrEnable: Entry card#%d\n", card); + + pmotorState = motor_state[card]; + pmotor = (struct vmex_motor *) (pmotorState->localaddr); + + status = devConnectInterruptVME(omsInterruptVector + card, + (void (*)(void *)) motorIsr, (void *) card); + + if (!RTN_SUCCESS(status)) + { + errPrintf(status, __FILE__, __LINE__, "Can't connect to vector %d\n", + omsInterruptVector + card); + pmotorState->irqdata->irqEnable = FALSE; /* Interrupts disable on card */ + pmotor->control = 0; + return (ERROR); + } + + status = devEnableInterruptLevel(OMS_INTERRUPT_TYPE, + omsInterruptLevel); + if (!RTN_SUCCESS(status)) + { + errPrintf(status, __FILE__, __LINE__, + "Can't enable enterrupt level %d\n", + omsInterruptLevel); + pmotorState->irqdata->irqEnable = FALSE; /* Interrupts disable on card */ + pmotor->control = 0; + return (ERROR); + } + + /* Setup card for interrupt-on-done */ + pmotor->vector = omsInterruptVector + card; + + + pmotorState->irqdata->recv_rng = rngCreate(OMS_RESP_Q_SZ); + pmotorState->irqdata->recv_sem = semBCreate(SEM_Q_PRIORITY, SEM_EMPTY); + + pmotorState->irqdata->send_rng = rngCreate(MAX_MSG_SIZE * 2); + pmotorState->irqdata->send_sem = semBCreate(SEM_Q_PRIORITY, SEM_EMPTY); + + pmotorState->irqdata->irqEnable = TRUE; + pmotorState->irqdata->irqErrno = 0; + + /* Clear board status */ + cardStatus = pmotor->status; + + /* enable interrupt-when-done and input-buffer-full interrupts */ + pmotor->control = IRQ_ENABLE_ALL; + + return (OK); +} + +STATIC void motorIsrDisable(int card) +{ + volatile struct controller *pmotorState; + volatile struct vmex_motor *pmotor; + long status; + + Debug(5, "motorIsrDisable: Entry card#%d\n", card); + + pmotorState = motor_state[card]; + pmotor = (struct vmex_motor *) (pmotorState->localaddr); + + /* Disable interrupts */ + pmotor->control = 0; + + status = devDisconnectInterruptVME(omsInterruptVector + card, + (void (*)(void *)) motorIsr); + if (!RTN_SUCCESS(status)) + errPrintf(status, __FILE__, __LINE__, "Can't disconnect vector %d\n", + omsInterruptVector + card); + + /* Remove interrupt control functions */ + pmotorState->irqdata->irqEnable = FALSE; + pmotorState->irqdata->irqErrno = 0; + rngDelete(pmotorState->irqdata->recv_rng); + rngDelete(pmotorState->irqdata->send_rng); + + semDelete(pmotorState->irqdata->recv_sem); + semDelete(pmotorState->irqdata->send_sem); +} + + +/*****************************************************/ +/* Configuration function for module_types data */ +/* areas. omsSetup() */ +/*****************************************************/ +int omsSetup(int num_cards, /* maximum number of cards in rack */ + int num_channels, /* Not used - Channels per card (4 or 8) */ + void *addrs, /* Base Address(0x0-0xb000 on 4K boundary) */ + unsigned vector, /* noninterrupting(0), valid vectors(64-255) */ + int int_level, /* interrupt level (1-6) */ + int scan_rate) /* polling rate - 1/60 sec units */ +{ + + if (num_cards < 1 || num_cards > OMS_NUM_CARDS) + oms44_num_cards = OMS_NUM_CARDS; + else + oms44_num_cards = num_cards; + + /* Check boundary(16byte) on base address */ + if ((uint32_t) addrs & 0xF) + { + Debug(1, "omsSetup: invalid base address 0x%X\n", (uint_t) addrs); + oms_addrs = (char *) OMS_NUM_ADDRS; + } + else + oms_addrs = (char *) addrs; + + omsInterruptVector = vector; + if (vector < 64 || vector > 255) + { + if (vector != 0) + { + Debug(1, "omsSetup: invalid interrupt vector %d\n", vector); + omsInterruptVector = (unsigned) OMS_INT_VECTOR; + } + } + + if (int_level < 1 || int_level > 6) + { + Debug(1, "omsSetup: invalid interrupt level %d\n", int_level); + omsInterruptLevel = OMS_INT_LEVEL; + } + else + omsInterruptLevel = int_level; + + /* Set motor polling task rate */ + if (scan_rate >= 1 && scan_rate <= sysClkRateGet()) + motor_scan_rate = sysClkRateGet() / scan_rate; + else + motor_scan_rate = SCAN_RATE; + return(0); +} + +/*****************************************************/ +/* initialize all software and hardware */ +/* motor_init() */ +/*****************************************************/ +STATIC int motor_init() +{ + volatile struct controller *pmotorState; + volatile struct vmex_motor *pmotor; + long status; + int card_index, motor_index, arg3, arg4; + char axis_pos[50], encoder_pos[50]; + char *tok_save, *pos_ptr; + int total_encoders = 0, total_axis = 0; + void *localaddr, *probeAddr; + + tok_save = NULL; + + /* Check for setup */ + if (oms44_num_cards <= 0) + { + Debug(1, "motor_init: *OMS driver disabled* \n omsSetup() is missing from startup script.\n"); + return (ERROR); + } + + /* allocate space for total number of motors */ + motor_state = (struct controller **) malloc(oms44_num_cards * + sizeof(struct controller *)); + + /* allocate structure space for each motor present */ + + total_cards = 0; + + if (rebootHookAdd((FUNCPTR) oms_reset) == ERROR) + Debug(1, "vme8/44 motor_init: oms_reset disabled\n"); + + for (card_index = 0; card_index < oms44_num_cards; card_index++) + { + Debug(2, "motor_init: card %d\n", card_index); + + probeAddr = oms_addrs + (card_index * OMS_BRD_SIZE); + Debug(9, "motor_init: locationProbe() on addr 0x%x\n", + (uint_t) probeAddr); + status = locationProbe(OMS_ADDRS_TYPE, (char *) probeAddr); + + if (PROBE_SUCCESS(status)) + { + status = devRegisterAddress(__FILE__, OMS_ADDRS_TYPE, + (size_t) probeAddr, OMS_BRD_SIZE, + (volatile void **) &localaddr); + Debug(9, "motor_init: devRegisterAddress() status = %d\n", + (int) status); + if (!RTN_SUCCESS(status)) + { + errPrintf(status, __FILE__, __LINE__, + "Can't register address 0x%x\n", probeAddr); + return (ERROR); + } + + Debug(9, "motor_init: localaddr = %x\n", (int) localaddr); + pmotor = (struct vmex_motor *) localaddr; + + total_cards++; + + Debug(9, "motor_init: malloc'ing motor_state\n"); + motor_state[card_index] = (struct controller *) malloc(sizeof(struct controller)); + pmotorState = motor_state[card_index]; + pmotorState->localaddr = (char *) localaddr; + pmotorState->motor_in_motion = 0; + + /* Disable Interrupts */ + pmotorState->irqdata = (struct irqdatastr *) malloc(sizeof(struct irqdatastr)); + pmotorState->irqdata->irqEnable = FALSE; + pmotor->control = 0; + + send_mess(card_index, "EF\n", (char) NULL); + send_mess(card_index, ERROR_CLEAR, (char) NULL); + send_mess(card_index, STOP_ALL, (char) NULL); + + send_mess(card_index, GET_IDENT, (char) NULL); + + recv_mess(card_index, (char *) pmotorState->ident, 1); + Debug(3, "Identification = %s\n", pmotorState->ident); + + send_mess(card_index, ALL_POS, (char) NULL); + recv_mess(card_index, axis_pos, 1); + + for (total_axis = 0, pos_ptr = strtok_r(axis_pos, ",", &tok_save); + pos_ptr; + pos_ptr = strtok_r(NULL, ",", &tok_save), total_axis++) + { + pmotorState->motor_info[total_axis].motor_motion = NULL; + pmotorState->motor_info[total_axis].status = 0; + } + + Debug(3, "Total axis = %d\n", total_axis); + pmotorState->total_axis = total_axis; + + /* + * Enable interrupt-when-done if selected - driver depends on + * motor_state->total_axis being set. + */ + if (omsInterruptVector) + { + if (motorIsrEnable(card_index) == ERROR) + errPrintf(0, __FILE__, __LINE__, "Interrupts Disabled!\n"); + } + + for (total_encoders = 0, motor_index = 0; motor_index < total_axis; motor_index++) + { + send_mess(card_index, ENCODER_QUERY, oms_trans_axis[motor_index]); + if (recv_mess(card_index, encoder_pos, 1) == -1) + { + /* Command error - no encoder */ + Debug(2, "No encoder on %d\n", motor_index); + pmotorState->motor_info[motor_index].encoder_present = NO; + } + else + { + total_encoders++; + pmotorState->motor_info[motor_index].encoder_present = YES; + } + } + + for (motor_index = 0; motor_index < total_axis; motor_index++) + { + pmotorState->motor_info[motor_index].status = 0; + pmotorState->motor_info[motor_index].no_motion_count = 0; + pmotorState->motor_info[motor_index].encoder_position = 0; + pmotorState->motor_info[motor_index].position = 0; + + if (pmotorState->motor_info[motor_index].encoder_present == YES) + pmotorState->motor_info[motor_index].status |= EA_PRESENT; + set_status(card_index, motor_index); + } + + Debug(2, "Init Address=0x%08.8x\n", (uint_t) localaddr); + Debug(3, "Total encoders = %d\n\n", (int) total_encoders); + } + else + { + Debug(3, "motor_init: Card NOT found!\n"); + pmotorState = (struct controller *) NULL; + } + } + + motor_sem = semBCreate(SEM_Q_PRIORITY, SEM_EMPTY); + any_motor_in_motion = 0; + + FASTLOCKINIT(&queue_lock); + FASTLOCK(&queue_lock); + mess_queue.head = (struct mess_node *) NULL; + mess_queue.tail = (struct mess_node *) NULL; + FASTUNLOCK(&queue_lock); + + FASTLOCKINIT(&freelist_lock); + FASTLOCK(&freelist_lock); + free_list.head = (struct mess_node *) NULL; + free_list.tail = (struct mess_node *) NULL; + FASTUNLOCK(&freelist_lock); + + Debug(3, "Motors initialized\n"); + + if (sizeof(int) >= sizeof(char *)) + { + arg3 = (int) (&oms_access); + arg4 = 0; + } + else + { + arg3 = (int) ((long) &oms_access >> 16); + arg4 = (int) ((long) &oms_access & 0xFFFF); + } + taskSpawn((char *) "Oms_motor", 64, VX_FP_TASK | VX_STDIO, 5000, motor_task, + motor_scan_rate, arg3, arg4, 0, 0, 0, 0, 0, 0, 0); + + Debug(3, "Started motor_task\n"); + return (0); +} + +/* Disables interrupts. Called on CTL X reboot. */ + +STATIC void oms_reset() +{ + short card; + struct vmex_motor *pmotor; + short status; + + for (card = 0; card < total_cards; card++) + { + pmotor = (struct vmex_motor *) motor_state[card]->localaddr; + if (vxMemProbe((char *) pmotor, READ, sizeof(short), (char *) &status) == OK) + pmotor->control &= 0x5f; + } +} + +/*---------------------------------------------------------------------*/ diff --git a/motorApp/OmsSrc/drvOms.h b/motorApp/OmsSrc/drvOms.h new file mode 100644 index 00000000..54ab1fc9 --- /dev/null +++ b/motorApp/OmsSrc/drvOms.h @@ -0,0 +1,95 @@ +/* +FILENAME... drvOms.h +USAGE... This file contains OMS driver "include" information that is + specific to OMS models VME8 and VME44. + +Version: $Revision: 1.1 $ +Modified By: $Author: sluiter $ +Last Modified: $Date: 2000-02-08 22:19:02 $ +*/ + +/* + * Experimental Physics and Industrial Control System (EPICS) + * + * Copyright 1991, the Regents of the University of California, + * and the University of Chicago Board of Governors. + * + * This software was produced under U.S. Government contracts: + * (W-7405-ENG-36) at the Los Alamos National Laboratory, + * and (W-31-109-ENG-38) at Argonne National Laboratory. + * + * Initial development by: + * The Controls and Automation Group (AT-8) + * Ground Test Accelerator + * Accelerator Technology Division + * Los Alamos National Laboratory + * + * Co-developed with + * The Controls and Computing Group + * Accelerator Systems Division + * Advanced Photon Source + * Argonne National Laboratory + * + * Modification Log: + * ----------------- + */ + +#ifndef INCdrvOmsh +#define INCdrvOmsh 1 + +#include "drvOmsCom.h" + +/* + * VME8/44 default profile + */ + +#define OMS_NUM_CARDS 8 +#define OMS_NUM_CHANNELS 8 +#define OMS_NUM_ADDRS 0xFC00 +#define OMS_BRD_SIZE 0x10 /* card address boundary */ +#define OMS_RESP_Q_SZ 0x100 /* maximum oms response message size */ + +/* status register */ +#define STAT_IRQ 0x80 +#define STAT_TRANS_BUF_EMPTY 0x40 +#define STAT_INPUT_BUF_FULL 0x20 +#define STAT_DONE 0x10 +#define STAT_OVERTRAVEL 0x08 +#define STAT_ENCODER_REQ 0x04 +#define STAT_UNUSED 0x02 +#define STAT_ERROR 0x01 + +/* done flag register */ +#define DONE_X 0x01 +#define DONE_Y 0x02 +#define DONE_Z 0x04 +#define DONE_T 0x08 +#define DONE_U 0x10 +#define DONE_V 0x20 +#define DONE_R 0x40 +#define DONE_S 0x80 + +/* interrupt control register */ +#define IRQ_ENABLE 0x80 +#define IRQ_TRANS_BUF 0x40 +#define IRQ_INPUT_BUF 0x20 +#define IRQ_DONE 0x10 + +#define IRQ_ENABLE_ALL (IRQ_ENABLE|IRQ_DONE|IRQ_INPUT_BUF) + +struct vmex_motor +{ + uint8_t unused0; + uint8_t data; + uint8_t unused1; + uint8_t done; + uint8_t unused2; + uint8_t control; + uint8_t unused3; + uint8_t status; + uint8_t unused4; + uint8_t vector; + uint8_t unused5[6]; +}; + +#endif /* INCdrvOmsh */ diff --git a/motorApp/OmsSrc/drvOms58.c b/motorApp/OmsSrc/drvOms58.c new file mode 100644 index 00000000..3dd78192 --- /dev/null +++ b/motorApp/OmsSrc/drvOms58.c @@ -0,0 +1,1125 @@ +/* +FILENAME... drvOms58.c +USAGE... Motor record driver level support for OMS model VME58. + +Version: $Revision: 1.1 $ +Modified By: $Author: sluiter $ +Last Modified: $Date: 2000-02-08 22:19:02 $ +*/ + +/* + * Original Author: Jim Kowalkowski + * Current Author: Joe Sullivan + * Date: 11/14/94 + * + * Experimental Physics and Industrial Control System (EPICS) + * + * Copyright 1991, the Regents of the University of California, + * and the University of Chicago Board of Governors. + * + * This software was produced under U.S. Government contracts: + * (W-7405-ENG-36) at the Los Alamos National Laboratory, + * and (W-31-109-ENG-38) at Argonne National Laboratory. + * + * Initial development by: + * The Controls and Automation Group (AT-8) + * Ground Test Accelerator + * Accelerator Technology Division + * Los Alamos National Laboratory + * + * Co-developed with + * The Controls and Computing Group + * Accelerator Systems Division + * Advanced Photon Source + * Argonne National Laboratory + * + * Modification Log: + * ----------------- + * .01 01-18-93 jbk initialized + * .02 11-14-94 jps copy drvOms.c and modify to point to vme58 driver + * ... + * .14 02-10-95 jps first released version w/interrupt supprt + * .15 02-16-95 jps add better simulation mode support + * .16 03-10-95 jps limit switch handling improvements + * .17 04-11-95 jps misc. fixes + * .18 09-27-95 jps fix GET_INFO latency bug, add axis argument to + * set_status() create add debug verbosity of 3 + * .19 05-03-96 jps convert 32bit card accesses to 16bit - vme58PCB + * version D does not support 32bit accesses. + * .20 06-20-96 jps add more security to message parameters (card #) + * .20a 02-19-97 tmm fixed for EPICS 3.13 + * V4.1 01-20-00 rls Update bit collision check and wait. Problem + * with old position data returned after Done + * detected when moving multiple axes on same + * controller board. Fix: Update shared memory + * data after Done detected via ASCII controller + * commands. To avoid collisions, skip writing to + * control reg. from motorIsr() if update bit is + * ON. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#ifdef __cplusplus +extern "C" { +#include +#include +#include +} +#else +#include +#include +#include +#endif +#include + +#include "motorRecord.h" /* For Driver Power Monitor feature only. */ +#include "motor.h" +#include "motordevCom.h" +#include "drvOms58.h" + +#define PRIVATE_FUNCTIONS 1 /* normal:1, debug:0 */ + +#define STATIC static + +/* Define for return test on locationProbe() */ +#define PROBE_SUCCESS(STATUS) ((STATUS)==S_dev_addressOverlap) + +/* jps: INFO messages - add RV and move QA to top */ +#define ALL_INFO "A? QA EA\n" /* jps: add RV */ +#define AXIS_INFO "A? QA\n" /* jps: add RV */ +#define ENCODER_QUERY "A? EA ID\n" +#define AXIS_CLEAR "A? CA\n" /* Clear done of addressed axis */ +#define DONE_QUERY "A? RA\n" /* ?? Is this needed?? */ +#define PID_QUERY "A? KK2 ID\n" + + +/*----------------debugging-----------------*/ +#ifdef DEBUG + #define Debug(l, f, args...) { if(l<=drvOms58debug) printf(f,## args); } +#else + #define Debug(l, f, args...) +#endif + +#define pack2x16(p) ((uint32_t)(((p[0])<<16)|(p[1]))) + +#ifdef __cplusplus +extern "C" long locationProbe(epicsAddressType, char *); +#else +extern long locationProbe(epicsAddressType, char *); +#endif + +/* Global data. */ +int oms58_num_cards = 0; +volatile int drvOms58debug = 0; + +/* Local data required for every driver; see "motordrvComCode.h" */ +#include "motordrvComCode.h" + +/* --- Local data common to all OMS drivers. --- */ +STATIC char *oms_addrs = 0x0; +STATIC volatile unsigned omsInterruptVector = 0; +STATIC volatile uint8_t omsInterruptLevel = OMS_INT_LEVEL; +STATIC volatile int max_io_tries = MAX_COUNT; +STATIC volatile int motionTO = 10; + +/* --- Local data specific to the Oms58 driver. --- */ +STATIC volatile int cmndBuffReadyWait = 4; +STATIC volatile int dataReadyWait = 1; +STATIC volatile int omsNanoRate = 2000; /* Aux clock rate for nanosleep */ + + +/*----------------functions-----------------*/ + +/* Common local function declarations. */ +static long report(int level); +static long init(); +static void query_done(int, int, struct mess_node *); +static int set_status(int card, int signal); +static int send_mess(int card, char const *com, char c); +static int recv_mess(int, char *, int); +static void motorIsr(int card); +static int motor_init(); +static void oms_reset(); + +STATIC void start_status(int card); +STATIC int motorIsrSetup(int card); +STATIC void oms_nanoSleep(int time); +STATIC int oms_nanoWakup(int val); + +/*----------------functions-----------------*/ + +struct driver_table oms58_access = +{ + NULL, + motor_send, + motor_free, + motor_card_info, + motor_axis_info, + &mess_queue, + &queue_lock, + &free_list, + &freelist_lock, + &motor_sem, + &motor_state, + &total_cards, + &any_motor_in_motion, + send_mess, + recv_mess, + set_status, + query_done, + start_status, + &initialized +}; + +struct +{ + long number; +#ifdef __cplusplus + long (*report) (int); + long (*init) (void); +#else + DRVSUPFUN report; + DRVSUPFUN init; +#endif +} drvOms58 = {2, report, init}; + +static long report(int level) +{ + int card; + + if (oms58_num_cards <= 0) + printf(" No VME58 controllers configured.\n"); + else + { + for (card = 0; card < oms58_num_cards; card++) + if (motor_state[card]) + printf(" Oms Vme58 motor card %d @ 0x%X, id: %s \n", card, + (uint_t) motor_state[card]->localaddr, + motor_state[card]->ident); + } + return (0); +} + +static long init() +{ + initialized = ON; /* Indicate that driver is initialized. */ + (void) motor_init(); + return ((long) 0); +} + + +STATIC void query_done(int card, int axis, struct mess_node *nodeptr) +{ + char buffer[40]; + + send_mess(card, DONE_QUERY, oms_trans_axis[axis]); + recv_mess(card, buffer, 1); + + if (nodeptr->status & RA_PROBLEM) + send_mess(card, AXIS_STOP, oms_trans_axis[axis]); +} + + +/********************************************************* + * Start card data area update. + * Each card will take 800-900us to complete update. + * start_status(int card) + * if card == -1 then start all cards + *********************************************************/ +STATIC void start_status(int card) +{ + volatile struct vmex_motor *pmotor; + CNTRL_REG cntrlReg; + int index; + + if (card >= 0) + { + if ((pmotor = (struct vmex_motor *) motor_state[card]->localaddr) != 0) + { + /* Wait Forever for controller to update. */ + cntrlReg.All = pmotor->control.cntrlReg; + while(cntrlReg.Bits.update != 0) + { + Debug(1, "start_status(): Update Wait: card #%d\n", card); + taskDelay(0); + cntrlReg.All = pmotor->control.cntrlReg; + }; + + cntrlReg.Bits.update = 1; + pmotor->control.cntrlReg = cntrlReg.All; + } + } + else + { + for (index = 0; index < total_cards; index++) + { + if ((pmotor = (struct vmex_motor *) motor_state[index]->localaddr) != 0) + { + /* Wait Forever for controller to update. */ + cntrlReg.All = pmotor->control.cntrlReg; + while(cntrlReg.Bits.update != 0) + { + Debug(1, "start_status(): Update Wait: card #%d\n", index); + taskDelay(0); + cntrlReg.All = pmotor->control.cntrlReg; + }; + + cntrlReg.Bits.update = 1; + pmotor->control.cntrlReg = cntrlReg.All; + } + } + } +} + +/************************************************************** + * Read motor status from card + * set_status() + ************************************************************/ + +STATIC int set_status(int card, int signal) +{ + struct mess_info *motor_info; + struct mess_node *nodeptr; + volatile struct vmex_motor *pmotor; + CNTRL_REG cntrlReg; + volatile MOTOR_DATA_REGS *pmotorData; + int32_t motorData; + /* Message parsing variables */ + char *p, *tok_save; + struct axis_status *ax_stat; + struct encoder_status *en_stat; + char q_buf[50], outbuf[50]; + int index; + + int rtn_state; + + motor_info = &(motor_state[card]->motor_info[signal]); + nodeptr = motor_info->motor_motion; + pmotor = (struct vmex_motor *) motor_state[card]->localaddr; + + if (motor_state[card]->motor_info[signal].encoder_present == YES) + { + /* get 4 peices of info from axis */ + send_mess(card, ALL_INFO, oms_trans_axis[signal]); + recv_mess(card, q_buf, 2); + } + else + { + /* get 2 peices of info from axis */ + send_mess(card, AXIS_INFO, oms_trans_axis[signal]); + recv_mess(card, q_buf, 1); + } + + for (index = 0, p = strtok_r(q_buf, ",", &tok_save); p; + p = strtok_r(NULL, ",", &tok_save), index++) + { + switch (index) + { + case 0: /* axis status */ + ax_stat = (struct axis_status *) p; + + if (ax_stat->direction == 'P') + motor_info->status |= RA_DIRECTION; + else + motor_info->status &= ~RA_DIRECTION; + + if (ax_stat->done == 'D') + { + /* Request Data Area Update after DONE detected so that both + * ASCII command data and shared memory data match. */ + start_status(card); + motor_info->status |= RA_DONE; + } + else + motor_info->status &= ~RA_DONE; + + if (ax_stat->overtravel == 'L') + motor_info->status |= RA_OVERTRAVEL; + else + motor_info->status &= ~RA_OVERTRAVEL; + + if (ax_stat->home == 'H') + motor_info->status |= RA_HOME; + else + motor_info->status &= ~RA_HOME; + + break; + case 1: /* encoder status */ + en_stat = (struct encoder_status *) p; + + if (en_stat->slip_enable == 'E') + motor_info->status |= EA_SLIP; + else + motor_info->status &= ~EA_SLIP; + + if (en_stat->pos_enable == 'E') + motor_info->status |= EA_POSITION; + else + motor_info->status &= ~EA_POSITION; + + if (en_stat->slip_detect == 'S') + motor_info->status |= EA_SLIP_STALL; + else + motor_info->status &= ~EA_SLIP_STALL; + + if (en_stat->axis_home == 'H') + motor_info->status |= EA_HOME; + else + motor_info->status &= ~EA_HOME; + break; + default: + break; + } + } + + + /* Setup mask for data-ready detection */ + cntrlReg.All = 0; + cntrlReg.Bits.update = 1; + + /* Wait for data area update to complete on card */ + while (cntrlReg.All & pmotor->control.cntrlReg) + oms_nanoSleep(dataReadyWait); + + pmotorData = &pmotor->data[signal]; + + /* Get motor position - word access only */ + motorData = pack2x16(pmotorData->cmndPos); + + if (motorData == motor_info->position) + motor_info->no_motion_count++; + else + { + motor_info->position = motorData; + motor_info->no_motion_count = 0; + } + + if (motor_info->no_motion_count > motionTO) + { + motor_info->status |= RA_PROBLEM; + send_mess(card, AXIS_STOP, oms_trans_axis[signal]); + motor_info->no_motion_count = 0; + errlogSevPrintf(errlogMinor, "Motor motion timeout ERROR on card: %d, signal: %d\n", + card, signal); + } + else + motor_info->status &= ~RA_PROBLEM; + + /* get command velocity - word access only */ + motorData = pack2x16(pmotorData->cmndVel); + + motor_info->velocity = motorData; + + if (!(motor_info->status & RA_DIRECTION)) + motor_info->velocity *= -1; + + /* Get encoder position */ + motorData = pack2x16(pmotorData->encPos); + + motor_info->encoder_position = motorData; + + if ((nodeptr != NULL) && ((motor_info->status & RA_OVERTRAVEL) == 0)) + { + struct motor_trans *trans = (struct motor_trans *) nodeptr->mrecord->dpvt; + if (trans->dpm == ON) + { + unsigned char bitselect; + unsigned char inputs = pmotor->control.ioLowReg; + bitselect = (1 << signal); + if ((inputs & bitselect) == 0) + { + motor_info->status |= RA_OVERTRAVEL; + logMsg((char *) "Drive power failure at VME58 card#%d motor#%d\n", + card, signal, 0, 0, 0, 0); + } + } + } + + rtn_state = (!motor_info->no_motion_count || + (motor_info->status & (RA_OVERTRAVEL | RA_DONE | RA_PROBLEM))) ? 1 : 0; + + /* Test for post-move string. */ + if ((motor_info->status & RA_DONE || motor_info->status & RA_OVERTRAVEL) && + (nodeptr != 0) && + (nodeptr->postmsgptr != 0)) + { + strcpy(outbuf, "A? "); + strcat(outbuf, nodeptr->postmsgptr); + strcat(outbuf, "\n"); + send_mess(card, outbuf, oms_trans_axis[signal]); + nodeptr->postmsgptr = NULL; + } + + return (rtn_state); +} + + +/*****************************************************/ +/* send a message to the OMS board */ +/* send_mess() */ +/*****************************************************/ +STATIC int send_mess(int card, char const *com, char inchar) +{ + volatile struct vmex_motor *pmotor; + int16_t putIndex; + int16_t deltaIndex; + char outbuf[MAX_MSG_SIZE], *p; + int return_code; + + if (strlen(com) > MAX_MSG_SIZE) + { + logMsg((char *) "drvOms58.cc:send_mess(); message size violation.\n", + 0, 0, 0, 0, 0, 0); + return (-1); + } + + /* Check that card exists */ + if (!motor_state[card]) + { + logMsg((char *) "drvOms58.cc:send_mess() - invalid card #%d\n", card, + 0, 0, 0, 0, 0); + return (-1); + } + + pmotor = (struct vmex_motor *) motor_state[card]->localaddr; + Debug(9, "send_mess: pmotor = %x\n", (uint_t) pmotor); + + return_code = 0; + + Debug(9, "send_mess: checking card %d status\n", card); + +/* jps: Command error handling has been moved to motorIsr() */ +/* Debug(1, "Command error detected! 0x%02x on send_mess\n", status.All); */ + + /* see if junk at input port - should not be any data available */ + if (pmotor->inGetIndex != pmotor->inPutIndex) + { + Debug(1, "send_mess - clearing data in buffer\n"); + recv_mess(card, NULL, -1); + } + + strcpy(outbuf, com); + if (inchar != (char) NULL) + outbuf[1] = inchar; /* put in axis */ + + Debug(9, "send_mess: ready to send message.\n"); + putIndex = pmotor->outPutIndex; + for (p = outbuf; *p != '\0'; p++) + { + pmotor->outBuffer[putIndex++] = *p; + if (putIndex >= BUFFER_SIZE) + putIndex = 0; + } + + Debug(4, "send_mess: sent card %d message:", card); + Debug(4, "%s\n", outbuf); + + pmotor->outPutIndex = putIndex; /* Message Sent */ + + while (pmotor->outPutIndex != pmotor->outGetIndex) + { + Debug(5, "send_mess: Waiting for ack: index delta=%d\n", + (((deltaIndex = pmotor->outPutIndex - pmotor->outGetIndex) < 0) ? + BUFFER_SIZE + deltaIndex : deltaIndex)); + /* after position command - latency = 4ms */ + oms_nanoSleep(cmndBuffReadyWait); + }; + + return (return_code); +} + +/* + * FUNCTION... recv_mess(int card, char *com, int amount) + * + * INPUT ARGUMENTS... + * card - + * *com - + * amount - + * + * LOGIC... + * IF controller card does not exist. + * ERROR Exit. + * ENDIF + * IF "amount" indicates buffer flush. + * WHILE characters left in input buffer. + * Remove characters from controller's input buffer. + * ENDWHILE + * NORMAL RETURN. + * ENDIF + * + * FOR each message requested (i.e. "amount"). + * Initialize head and tail pointers. + * Initialize local buffer "get" index. + * FOR + * IF characters left in controller's input buffer. + * + * ENDIF + * ENDFOR + * ENDFOR + * + */ +STATIC int recv_mess(int card, char *com, int amount) +{ + volatile struct vmex_motor *pmotor; + int16_t getIndex; + int i, trys; + char junk; + unsigned char inchar; + int piece, head_size, tail_size; + + /* Check that card exists */ + if (!motor_state[card]) + { + Debug(1, "resv_mess - invalid card #%d\n", card); + return (-1); + } + + pmotor = (struct vmex_motor *) motor_state[card]->localaddr; + + if (amount == -1) + { + Debug(7, "-------------"); + getIndex = pmotor->inGetIndex; + for (i = 0, trys = 0; trys < max_io_tries; trys++) + { + while (getIndex != pmotor->inPutIndex) + { + junk = pmotor->inBuffer[getIndex++]; + /* handle circular buffer */ + if (getIndex >= BUFFER_SIZE) + getIndex = 0; + + Debug(7, "%c", junk); + trys = 0; + i++; + } + } + pmotor->inGetIndex = getIndex; + + Debug(7, "-------------"); + Debug(1, "\nrecv_mess - cleared %d error data\n", i); + return (0); + } + + for (i = 0; amount > 0; amount--) + { + Debug(7, "-------------"); + head_size = 0; + tail_size = 0; + + getIndex = pmotor->inGetIndex; + + for (piece = 0, trys = 0; piece < 3 && trys < max_io_tries; trys++) + { + if (getIndex != pmotor->inPutIndex) + { + inchar = pmotor->inBuffer[getIndex++]; + if (getIndex >= BUFFER_SIZE) + getIndex = 0; + + Debug(7, "%02x", inchar); + + switch (piece) + { + case 0: /* header */ + if (inchar == '\n' || inchar == '\r') + head_size++; + else + { + piece++; + com[i++] = inchar; + } + break; + case 1: /* body */ + if (inchar == '\n' || inchar == '\r') + { + piece++; + tail_size++; + } + else + com[i++] = inchar; + break; + + case 2: /* trailer */ + tail_size++; + if (tail_size >= head_size) + piece++; + break; + } + trys = 0; + } + } + pmotor->inGetIndex = getIndex; + + Debug(7, "-------------\n"); + if (trys >= max_io_tries) + { + Debug(1, "Timeout occurred in recv_mess\n"); + com[i] = '\0'; + + /* ----------------extra junk-------------- */ + /* jps: command error now cleared by motorIsr() */ + /* ----------------extra junk-------------- */ + + return (-1); + } + com[i++] = ','; + } + + if (i > 0) + com[i - 1] = '\0'; + else + com[i] = '\0'; + + Debug(4, "recv_mess: card %d", card); + Debug(4, " com %s\n", com); + return (0); +} + + +/*****************************************************/ +/* Configuration function for module_types data */ +/* areas. omsSetup() */ +/*****************************************************/ +int oms58Setup(int num_cards, /* maximum number of cards in rack */ + int num_channels,/* Not used - Channels per card (4 or 8) */ + void *addrs, /* Base Address(0x0-0xb000 on 4K boundary) */ + unsigned vector, /* noninterrupting(0), valid vectors(64-255) */ + int int_level, /* interrupt level (1-6) */ + int scan_rate) /* polling rate - 1/60 sec units */ +{ + if (num_cards < 1 || num_cards > OMS_NUM_CARDS) + oms58_num_cards = OMS_NUM_CARDS; + else + oms58_num_cards = num_cards; + + /* Check range and boundary(4K) on base address */ + if (addrs > (void *) 0xF000 || ((uint32_t) addrs & 0xFFF)) + { + Debug(1, "omsSetup: invalid base address 0x%X\n", (uint_t) addrs); + oms_addrs = (char *) OMS_NUM_ADDRS; + } + else + oms_addrs = (char *) addrs; + + omsInterruptVector = vector; + if (vector < 64 || vector > 255) + { + if (vector != 0) + { + Debug(1, "omsSetup: invalid interrupt vector %d\n", vector); + omsInterruptVector = (unsigned) OMS_INT_VECTOR; + } + } + + if (int_level < 1 || int_level > 6) + { + Debug(1, "omsSetup: invalid interrupt level %d\n", int_level); + omsInterruptLevel = OMS_INT_LEVEL; + } + else + omsInterruptLevel = int_level; + + /* Set motor polling task rate */ + if (scan_rate >= 1 && scan_rate <= sysClkRateGet()) + motor_scan_rate = sysClkRateGet() / scan_rate; + else + motor_scan_rate = SCAN_RATE; + return(0); +} + + +/*****************************************************/ +/* Interrupt service routine. */ +/* motorIsr() */ +/*****************************************************/ +STATIC void motorIsr(int card) +{ + volatile struct controller *pmotorState; + volatile struct vmex_motor *pmotor; + STATUS_REG statusBuf; + uint8_t doneFlags, userIO, slipFlags, limitFlags, cntrlReg; + + if (card >= total_cards || (pmotorState = motor_state[card]) == NULL) + { + logMsg((char *) "Invalid entry-card #%d\n", card, 0, 0, 0, 0, 0); + return; + } + + pmotor = (struct vmex_motor *) (pmotorState->localaddr); + + /* Status register - clear irqs on read. */ + statusBuf.All = pmotor->control.statusReg; + + /* Done register - clears on read */ + doneFlags = pmotor->control.doneReg; + + /* UserIO register - clears on read */ + userIO = pmotor->control.ioLowReg; + +/* Questioniable Fix for undefined problem Starts Here. The following + * code is meant to fix a problem that exhibted "spurious" interrupts + * and "stuck in the Oms ISR" behavior. +*/ + /* Slip detection - clear on read */ + slipFlags = pmotor->control.slipReg; + + /* Overtravel - clear on read */ + limitFlags = pmotor->control.limitReg; + + /* Only write control register if update bit is OFF. */ + /* Assure proper control register settings */ + cntrlReg = pmotor->control.cntrlReg; + if ((cntrlReg & 0x01) == 0) + pmotor->control.cntrlReg = (uint8_t) 0x90; +/* Questioniable Fix for undefined problem Ends Here. */ + + if (drvOms58debug >= 10) + logMsg((char *) "entry card #%d,status=0x%X,done=0x%X\n", card, + statusBuf.All, doneFlags, 0, 0, 0); + + /* Motion done handling */ + if (statusBuf.Bits.done) + /* Wake up polling task 'motor_task()' to issue callbacks */ + semGive(motor_sem); + + if (statusBuf.Bits.cmndError) + logMsg((char *) "command error detected by motorISR() on card %d\n", + card, 0, 0, 0, 0, 0); +} + +STATIC int motorIsrSetup(int card) +{ + volatile struct vmex_motor *pmotor; + long status; + CNTRL_REG cntrlBuf; + + Debug(5, "motorIsrSetup: Entry card#%d\n", card); + + pmotor = (struct vmex_motor *) (motor_state[card]->localaddr); + + status = devConnectInterruptVME(omsInterruptVector + card, + (void (*)(void *)) motorIsr, (void *) card); + if (!RTN_SUCCESS(status)) + { + errPrintf(status, __FILE__, __LINE__, "Can't connect to vector %d\n", omsInterruptVector + card); + omsInterruptVector = 0; /* Disable interrupts */ + cntrlBuf.All = 0; + pmotor->control.cntrlReg = cntrlBuf.All; + return (ERROR); + } + + status = devEnableInterruptLevel(OMS_INTERRUPT_TYPE, + omsInterruptLevel); + if (!RTN_SUCCESS(status)) + { + errPrintf(status, __FILE__, __LINE__, "Can't enable enterrupt level %d\n", omsInterruptLevel); + omsInterruptVector = 0; /* Disable interrupts */ + cntrlBuf.All = 0; + pmotor->control.cntrlReg = cntrlBuf.All; + return (ERROR); + } + + /* Setup card for interrupt-on-done */ + pmotor->control.intVector = omsInterruptVector + card; + + /* enable interrupt-when-done irq */ + cntrlBuf.All = 0; + cntrlBuf.Bits.doneIntEna = 1; + cntrlBuf.Bits.intReqEna = 1; + + pmotor->control.cntrlReg = cntrlBuf.All; + return (OK); +} + +/*****************************************************/ +/* initialize all software and hardware */ +/* motor_init() */ +/*****************************************************/ +STATIC int motor_init() +{ + volatile struct controller *pmotorState; + volatile struct vmex_motor *pmotor; + STATUS_REG statusReg; + int8_t omsReg; + long status; + int card_index, motor_index, arg3, arg4; + char axis_pos[50], encoder_pos[50]; + char *tok_save, *pos_ptr; + int total_encoders = 0, total_axis = 0, total_pidcnt = 0; + volatile void *localaddr; + void *probeAddr; + + tok_save = NULL; + + /* Check for setup */ + if (oms58_num_cards <= 0) + { + Debug(1, "motor_init: *OMS58 driver disabled* \n oms58Setup() is missing from startup script.\n"); + return (ERROR); + } + + /* allocate space for total number of motors */ + motor_state = (struct controller **) malloc(oms58_num_cards * + sizeof(struct controller *)); + + /* allocate structure space for each motor present */ + + total_cards = 0; + + if (rebootHookAdd((FUNCPTR) oms_reset) == ERROR) + Debug(1, "vme58 motor_init: oms_reset disabled\n"); + + for (card_index = 0; card_index < oms58_num_cards; card_index++) + { + int8_t *startAddr; + int8_t *endAddr; + + Debug(2, "motor_init: card %d\n", card_index); + + probeAddr = (void *) (oms_addrs + (card_index * OMS_BRD_SIZE)); + startAddr = (int8_t *) probeAddr; + endAddr = startAddr + OMS_BRD_SIZE; + + Debug(9, "motor_init: locationProbe() on addr 0x%x\n", (uint_t) probeAddr); + /* Perform scan of vme58 memory space to assure card id */ + do + { + status = locationProbe(OMS_ADDRS_TYPE, (char *) startAddr); + startAddr += 0x100; + } while (PROBE_SUCCESS(status) && startAddr < endAddr); + + if (PROBE_SUCCESS(status)) + { + status = devRegisterAddress(__FILE__, OMS_ADDRS_TYPE, + (size_t) probeAddr, OMS_BRD_SIZE, + (volatile void **) &localaddr); + Debug(9, "motor_init: devRegisterAddress() status = %d\n", (int) status); + if (!RTN_SUCCESS(status)) + { + errPrintf(status, __FILE__, __LINE__, "Can't register address 0x%x\n", probeAddr); + return (ERROR); + } + + Debug(9, "motor_init: localaddr = %x\n", (uint_t) localaddr); + pmotor = (struct vmex_motor *) localaddr; + + total_cards++; + + Debug(9, "motor_init: malloc'ing motor_state\n"); + motor_state[card_index] = (struct controller *) malloc(sizeof(struct controller)); + pmotorState = motor_state[card_index]; + pmotorState->localaddr = (char *) localaddr; + pmotorState->motor_in_motion = 0; + + pmotorState->irqdata = (struct irqdatastr *) NULL; + /* Disable all interrupts */ + pmotor->control.cntrlReg = 0; + + send_mess(card_index, "EF\n", (char) NULL); + send_mess(card_index, ERROR_CLEAR, (char) NULL); + send_mess(card_index, STOP_ALL, (char) NULL); + + send_mess(card_index, GET_IDENT, (char) NULL); + recv_mess(card_index, (char *) pmotorState->ident, 1); + Debug(3, "Identification = %s\n", pmotorState->ident); + + send_mess(card_index, ALL_POS, (char) NULL); + recv_mess(card_index, axis_pos, 1); + + for (total_axis = 0, pos_ptr = strtok_r(axis_pos, ",", &tok_save); + pos_ptr; pos_ptr = strtok_r(NULL, ",", &tok_save), total_axis++) + { + pmotorState->motor_info[total_axis].motor_motion = NULL; + pmotorState->motor_info[total_axis].status = 0; + } + + Debug(3, "motor_init: Total axis = %d\n", total_axis); + pmotorState->total_axis = total_axis; + + /* Assure done is cleared */ + statusReg.All = pmotor->control.statusReg; + omsReg = pmotor->control.doneReg; + for (total_encoders = total_pidcnt = 0, motor_index = 0; motor_index < total_axis; motor_index++) + { + /* Test if motor has an encoder. */ + send_mess(card_index, ENCODER_QUERY, oms_trans_axis[motor_index]); + while (!pmotor->control.doneReg) /* Wait for command to complete. */ + oms_nanoSleep(1); + + statusReg.All = pmotor->control.statusReg; + + if (statusReg.Bits.cmndError) + { + Debug(2, "motor_init: No encoder on axis %d\n", motor_index); + pmotorState->motor_info[motor_index].encoder_present = NO; + } + else + { + total_encoders++; + pmotorState->motor_info[motor_index].encoder_present = YES; + recv_mess(card_index, encoder_pos, 1); + } + + /* Test if motor has PID parameters. */ + send_mess(card_index, PID_QUERY, oms_trans_axis[motor_index]); + do /* Wait for command to complete. */ + { + taskDelay(1); + statusReg.All = pmotor->control.statusReg; + } while(statusReg.Bits.done == 0); + + + if (statusReg.Bits.cmndError) + { + Debug(2, "motor_init: No PID parameters on axis %d\n", motor_index); + pmotorState->motor_info[motor_index].pid_present = NO; + } + else + { + total_pidcnt++; + pmotorState->motor_info[motor_index].pid_present = YES; + } + } + + /* Enable interrupt-when-done if selected */ + if (omsInterruptVector) + { + if (motorIsrSetup(card_index) == ERROR) + errPrintf(0, __FILE__, __LINE__, "Interrupts Disabled!\n"); + } + + start_status(card_index); + for (motor_index = 0; motor_index < total_axis; motor_index++) + { + pmotorState->motor_info[motor_index].status = 0; + pmotorState->motor_info[motor_index].no_motion_count = 0; + pmotorState->motor_info[motor_index].encoder_position = 0; + pmotorState->motor_info[motor_index].position = 0; + + if (pmotorState->motor_info[motor_index].encoder_present == YES) + pmotorState->motor_info[motor_index].status |= EA_PRESENT; + if (pmotorState->motor_info[motor_index].pid_present == YES) + pmotorState->motor_info[motor_index].status |= GAIN_SUPPORT; + + set_status(card_index, motor_index); + + send_mess(card_index, DONE_QUERY, oms_trans_axis[motor_index]); /* Is this needed??? */ + recv_mess(card_index, axis_pos, 1); + } + + Debug(2, "motor_init: Init Address=0x%08.8x\n", (uint_t) localaddr); + Debug(3, "motor_init: Total encoders = %d\n", total_encoders); + Debug(3, "motor_init: Total with PID = %d\n", total_pidcnt); + } + else + { + Debug(3, "motor_init: Card NOT found!\n"); + motor_state[card_index] = (struct controller *) NULL; + } + } + + motor_sem = semBCreate(SEM_Q_PRIORITY, SEM_EMPTY); + any_motor_in_motion = 0; + + FASTLOCKINIT(&queue_lock); + FASTLOCK(&queue_lock); + mess_queue.head = (struct mess_node *) NULL; + mess_queue.tail = (struct mess_node *) NULL; + FASTUNLOCK(&queue_lock); + + FASTLOCKINIT(&freelist_lock); + FASTLOCK(&freelist_lock); + free_list.head = (struct mess_node *) NULL; + free_list.tail = (struct mess_node *) NULL; + FASTUNLOCK(&freelist_lock); + + Debug(3, "Motors initialized\n"); + + if (sizeof(int) >= sizeof(char *)) + { + arg3 = (int) (&oms58_access); + arg4 = 0; + } + else + { + arg3 = (int) ((long) &oms58_access >> 16); + arg4 = (int) ((long) &oms58_access & 0xFFFF); + } + taskSpawn((char *) "Oms58_motor", 64, VX_FP_TASK | VX_STDIO, 5000, motor_task, + motor_scan_rate, arg3, arg4, 0, 0, 0, 0, 0, 0, 0); + + Debug(3, "Started motor_task\n"); + return (0); +} + + +/* Disables interrupts. Called on CTL X reboot. */ + +STATIC void oms_reset() +{ + short card; + volatile struct vmex_motor *pmotor; + short status; + CNTRL_REG cntrlBuf; + + for (card = 0; card < total_cards; card++) + { + pmotor = (struct vmex_motor *) motor_state[card]->localaddr; + if (vxMemProbe((char *) pmotor, READ, sizeof(short), (char *) &status) == OK) + { + cntrlBuf.All = pmotor->control.cntrlReg; + cntrlBuf.Bits.intReqEna = 0; + pmotor->control.cntrlReg = cntrlBuf.All; + } + } +} + +STATIC SEM_ID nanoSem; +STATIC int nanoTime; + +/* Sleep for about 1ms - MVME167 vxWorks dependant */ +STATIC void oms_nanoSleep(int time) +{ + if (FALSE) +/* if (time > 0) */ + { + nanoSem = semBCreate(SEM_Q_PRIORITY, SEM_EMPTY); + nanoTime = time; + + sysAuxClkDisable(); + sysAuxClkRateSet(omsNanoRate); /* tick = 1ms */ + sysAuxClkConnect(&oms_nanoWakup, 0); + sysAuxClkEnable(); + + semTake(nanoSem, 1); /* Maximum sleep time = 1 system clock tick */ + sysAuxClkDisable(); + semDelete(nanoSem); + } +} + +STATIC int oms_nanoWakup(int val) +{ + if (--nanoTime <= 0) + semGive(nanoSem); + return(0); +} + + +/*---------------------------------------------------------------------*/ diff --git a/motorApp/OmsSrc/drvOms58.h b/motorApp/OmsSrc/drvOms58.h new file mode 100644 index 00000000..b31ba093 --- /dev/null +++ b/motorApp/OmsSrc/drvOms58.h @@ -0,0 +1,261 @@ +/* +FILENAME... drvOms58.h +USAGE... OMS driver level "include" information that is specific to OMS + model VME58. + +Version: $Revision: 1.1 $ +Modified By: $Author: sluiter $ +Last Modified: $Date: 2000-02-08 22:19:03 $ +*/ + +/* + * Original Author: Jim Kowalkowski + * Current Author: Joe Sullivan + * Date: 11/14/94 + * + * Experimental Physics and Industrial Control System (EPICS) + * + * Copyright 1991, the Regents of the University of California, + * and the University of Chicago Board of Governors. + * + * This software was produced under U.S. Government contracts: + * (W-7405-ENG-36) at the Los Alamos National Laboratory, + * and (W-31-109-ENG-38) at Argonne National Laboratory. + * + * Initial development by: + * The Controls and Automation Group (AT-8) + * Ground Test Accelerator + * Accelerator Technology Division + * Los Alamos National Laboratory + * + * Co-developed with + * The Controls and Computing Group + * Accelerator Systems Division + * Advanced Photon Source + * Argonne National Laboratory + * + * Modification Log: + * ----------------- + * .01 01-18-93 jbk initialized + * .02 11-14-94 jps copy drvOms.c and modify to point to vme58 driver + * ... + * .06 12-07-94 jps first released version w/interrupt supprt + * .07 12-20-94 jps rearrange the device init routines + * .08 05-03-96 jps convert 32bit card accesses to 16bit - vme58PCB + * version D does not support 32bit accesses. + * .09 05-09-97 jps increase maximum card count to 15 + * + */ + +#ifndef INCdrvOms58h +#define INCdrvOms58h 1 + +#include "drvOmsCom.h" + +/* + * VME58 default profile + */ + +#define OMS_NUM_CARDS 15 +#define OMS_NUM_CHANNELS 8 +#define OMS_NUM_ADDRS 0x4000 +#define OMS_BRD_SIZE 0x1000 /* card address boundary */ + +#define BUFFER_SIZE 256 + +/* Board control register structures */ + + +/* VME58 DUAL-PORT MEMORY MAP */ +typedef struct +{ + uint16_t encPos[2]; + uint16_t cmndPos[2]; + uint16_t cmndVel[2]; + uint16_t accel[2]; + uint16_t maxVel[2]; + uint16_t baseVel[2]; + uint16_t dFltrGain[2]; + uint16_t dFltrPole[2]; + uint16_t dFltrZero[2]; + uint16_t reserved[46]; +} MOTOR_DATA_REGS; + +/* Definitions for VME58 I/O Registers */ + +/* Control Register - Offset = 0x0FE1 */ +typedef union +{ + uint8_t All; + struct + { + uint8_t intReqEna:1; /* Master interrupt request enable */ + uint8_t ioIntEna:1; /* I/O bits 0 and 1 interrupt enable */ + uint8_t directInEna:1; /* Interrupt request to the VME58 ? */ + uint8_t doneIntEna:1; /* Done detect interrupt enable */ + uint8_t otIntEna:1; /* Overtravel detect interrupt enable */ + uint8_t slipIntEna:1; /* Encoder slip detect interrupt enable */ + uint8_t:1; /* Unused */ + uint8_t update:1; /* Data area update request */ + } Bits; +} CNTRL_REG; + +/* Status Register - Offset = 0x0FE3 */ +typedef union +{ + uint8_t All; + struct + { + uint8_t interrupt:1; /* Interrupt dectect */ + uint8_t directIn:1; /* Direct input interrupt detect */ + uint8_t directOut:1; /* Direct ouput interrupt detect */ + uint8_t done:1; /* Motion done detect */ + uint8_t overtravel:1; /* Overtravel detect */ + uint8_t encoderSlip:1; /* Encoder slip detect */ + uint8_t cardOK:1; /* Powerup initilization complete */ + uint8_t cmndError:1; /* Command error dectect */ + } Bits; +} STATUS_REG; + +/* I/O Register(0-7) - Offset = 0x0FE5 */ +typedef union +{ + uint8_t All; + struct + { + uint8_t io_7:1; /* Bit 7 */ + uint8_t io_6:1; /* Bit 6 */ + uint8_t io_5:1; /* Bit 5 */ + uint8_t io_4:1; /* Bit 4 */ + uint8_t io_3:1; /* Bit 3 */ + uint8_t io_2:1; /* Bit 2 */ + uint8_t io_1:1; /* Bit 1 */ + uint8_t io_0:1; /* Bit 0 */ + } Bits; +} IO_LOW_REG; + +/* Slip Flag Register - Offset = 0x0FE7 */ +typedef union +{ + uint8_t All; + struct + { + uint8_t slip_s:1; /* status of S axis */ + uint8_t slip_r:1; /* status of R axis */ + uint8_t slip_v:1; /* status of V axis */ + uint8_t slip_u:1; /* status of U axis */ + uint8_t slip_t:1; /* status of T axis */ + uint8_t slip_z:1; /* status of Z axis */ + uint8_t slip_y:1; /* status of Y axis */ + uint8_t slip_x:1; /* status of X axis */ + } Bits; +} SLIP_REG; + +/* Done Flag Register - Offset = 0x0FE9 */ +typedef union +{ + uint8_t All; + struct + { + uint8_t done_s:1; /* status of S axis */ + uint8_t done_r:1; /* status of R axis */ + uint8_t done_v:1; /* status of V axis */ + uint8_t done_u:1; /* status of U axis */ + uint8_t done_t:1; /* status of T axis */ + uint8_t done_z:1; /* status of Z axis */ + uint8_t done_y:1; /* status of Y axis */ + uint8_t done_x:1; /* status of X axis */ + } Bits; +} DONE_REG; + +/* I/O High Register(8-13) - Offset = 0x0FEB */ +typedef union +{ + uint8_t All; + struct + { + uint8_t:1; /* Unused */ + uint8_t:1; /* Unused */ + uint8_t io_13:1; /* Bit 13 */ + uint8_t io_12:1; /* Bit 12 */ + uint8_t io_11:1; /* Bit 11 */ + uint8_t io_10:1; /* Bit 10 */ + uint8_t io_9:1; /* Bit 9 */ + uint8_t io_8:1; /* Bit 8 */ + } Bits; +} IO_HIGH_REG; + + +/* Limit Switch Status Register - Offset = 0x0FED */ +typedef union +{ + uint8_t All; + struct + { + uint8_t limit_s:1; /* status of S axis */ + uint8_t limit_r:1; /* status of R axis */ + uint8_t limit_v:1; /* status of V axis */ + uint8_t limit_u:1; /* status of U axis */ + uint8_t limit_t:1; /* status of T axis */ + uint8_t limit_z:1; /* status of Z axis */ + uint8_t limit_y:1; /* status of Y axis */ + uint8_t limit_x:1; /* status of X axis */ + } Bits; +} LIMIT_REG; + +/* Home Switch Status Register - Offset = 0x0FEF */ +typedef union +{ + uint8_t All; + struct + { + uint8_t home_s:1; /* status of S axis */ + uint8_t home_r:1; /* status of R axis */ + uint8_t home_v:1; /* status of V axis */ + uint8_t home_u:1; /* status of U axis */ + uint8_t home_t:1; /* status of T axis */ + uint8_t home_z:1; /* status of Z axis */ + uint8_t home_y:1; /* status of Y axis */ + uint8_t home_x:1; /* status of X axis */ + } Bits; +} HOME_REG; + +typedef struct +{ + uint8_t unused00; + uint8_t cntrlReg; /* Control Register - Read/Write */ + uint8_t unused02; + uint8_t statusReg; /* Status Register - Read */ + uint8_t unused04; + uint8_t ioLowReg; /* IO bits 0-7 status register - Read */ + uint8_t unused06; + uint8_t slipReg; /* Encoder slip status register - Read */ + uint8_t unused08; + uint8_t doneReg; /* Axis done status register - Read */ + uint8_t unused0A; + uint8_t ioHighReg; /* IO bits 8-13 status register - Read */ + uint8_t unused0C; + uint8_t limitReg; /* Limit switch status register - Read */ + uint8_t unused0E; + uint8_t homeReg; /* Home switch status register - Read */ + uint8_t unusedF0; + uint8_t intVector; /* Interrupt vector */ +} MOTOR_CNTRL_REGS; + + +/* OMS VME dual port memory map */ +struct vmex_motor +{ + int16_t inPutIndex; + int16_t outGetIndex; + int16_t inBuffer[BUFFER_SIZE]; + int16_t reserved0[254]; + MOTOR_DATA_REGS data[OMS_NUM_CHANNELS]; + int16_t outPutIndex; + int16_t inGetIndex; + int16_t outBuffer[BUFFER_SIZE]; + int16_t reserved1[750]; + MOTOR_CNTRL_REGS control; +}; + +#endif /* INCdrvOms58h */ diff --git a/motorApp/OmsSrc/drvOmsCom.h b/motorApp/OmsSrc/drvOmsCom.h new file mode 100644 index 00000000..cad450ab --- /dev/null +++ b/motorApp/OmsSrc/drvOmsCom.h @@ -0,0 +1,62 @@ +/* +FILENAME... drvOmsCom.h +USAGE... This file contains OMS driver "include" information + that is common to all OMS models. + +Version: $Revision: 1.1 $ +Modified By: $Author: sluiter $ +Last Modified: $Date: 2000-02-08 22:19:03 $ +*/ + +/* + * Original Author: Ron Sluiter + * Date: 12/18/98 + * + * Experimental Physics and Industrial Control System (EPICS) + * + * Copyright 1991, the Regents of the University of California, + * and the University of Chicago Board of Governors. + * + * This software was produced under U.S. Government contracts: + * (W-7405-ENG-36) at the Los Alamos National Laboratory, + * and (W-31-109-ENG-38) at Argonne National Laboratory. + * + * Initial development by: + * The Controls and Automation Group (AT-8) + * Ground Test Accelerator + * Accelerator Technology Division + * Los Alamos National Laboratory + * + * Co-developed with + * The Controls and Computing Group + * Accelerator Systems Division + * Advanced Photon Source + * Argonne National Laboratory + * + * Modification Log: + * ----------------- + */ + +#ifndef INCdrvOmsComh +#define INCdrvOmsComh 1 + +#include "motordrvCom.h" + +/* Default profile. */ + +#define OMS_INTERRUPT_TYPE intVME +#define OMS_ADDRS_TYPE atVMEA16 +#define OMS_INT_VECTOR 180 /* default interrupt vector (64-255) */ +#define OMS_INT_LEVEL 5 /* default interrupt level (1-6) */ + +/* OMS Command strings. */ +#define AXIS_STOP "A? ST\n" +#define GET_IDENT "WY\n" +#define ERROR_CLEAR "IC\n" +#define STOP_ALL "AA SA\n" +#define ALL_POS "AA RP\n" + +/* Global data. */ +extern char oms_trans_axis[]; + +#endif /* INCdrvOmsComh */ diff --git a/motorApp/SoftMotorSrc/Makefile b/motorApp/SoftMotorSrc/Makefile new file mode 100644 index 00000000..0380f4bf --- /dev/null +++ b/motorApp/SoftMotorSrc/Makefile @@ -0,0 +1,3 @@ +TOP=../.. +include $(TOP)/config/CONFIG_APP +include $(TOP)/config/RULES_ARCHS diff --git a/motorApp/SoftMotorSrc/Makefile.Host b/motorApp/SoftMotorSrc/Makefile.Host new file mode 100644 index 00000000..6d40904a --- /dev/null +++ b/motorApp/SoftMotorSrc/Makefile.Host @@ -0,0 +1,11 @@ +# Makefile.Host +TOP = ../../.. +include $(TOP)/config/CONFIG_APP +#---------------------------------------- +# ADD MACRO DEFINITIONS AFTER THIS LINE + +DBDINSTALL += devSoftMotor.dbd + +include $(TOP)/config/RULES.Host +#---------------------------------------- +# ADD RULES AFTER THIS LINE diff --git a/motorApp/SoftMotorSrc/Makefile.Vx b/motorApp/SoftMotorSrc/Makefile.Vx new file mode 100644 index 00000000..0a6e1e98 --- /dev/null +++ b/motorApp/SoftMotorSrc/Makefile.Vx @@ -0,0 +1,18 @@ +# Makefile.Vx +TOP = ../../.. +include $(TOP)/config/CONFIG_APP +#---------------------------------------- +# ADD MACRO DEFINITIONS AFTER THIS LINE + +# The following is required for the Soft Channel (i.e., MotorSoft) device driver. +SRCS.c += ../devSoft.c ../devSoftAux.c +LIBOBJS += devSoft.o devSoftAux.o + +LIBNAME = SoftMotorLib + +#Note that the command line that builds the +#library $(LIBNAME) may be HUGE (>3kB) +# +include $(TOP)/config/RULES.Vx +#---------------------------------------- +# ADD RULES AFTER THIS LINE diff --git a/motorApp/SoftMotorSrc/devSoft.c b/motorApp/SoftMotorSrc/devSoft.c new file mode 100644 index 00000000..a059324a --- /dev/null +++ b/motorApp/SoftMotorSrc/devSoft.c @@ -0,0 +1,185 @@ +/* +FILENAME... devSoft.c +USAGE... Motor record device level support for Soft channel. + +Modified By: $Author: sluiter $ +Last Modified: $Date: 2000-02-08 22:19:25 $ +*/ + +/* + * Original Author: Ron Sluiter + * Date: 06/15/99 + * + * Experimental Physics and Industrial Control System (EPICS) + * + * Copyright 1991, the University of Chicago Board of Governors. + * + * This software was produced under U.S. Government contract + * W-31-109-ENG-38 at Argonne National Laboratory. + * + * Beamline Controls & Data Acquisition Group + * Experimental Facilities Division + * Advanced Photon Source + * Argonne National Laboratory + * + * Modification Log: + * ----------------- + */ + + +/* +NOTES... +- Can't call CA functions until after dbLockInitRecords() has + been called and initialized lock sets. +*/ + + +#include +#include +#include +#ifdef __cplusplus +extern "C" { +#include +#include +} +#else +#include +#include +#endif + +#include "motorRecord.h" +#include "motor.h" +#include "devSoft.h" + +#define STATIC static + +STATIC long update(struct motorRecord *); +STATIC long start(struct motorRecord *); +STATIC long build(motor_cmnd, double *, struct motorRecord *); +STATIC long end(struct motorRecord *); +STATIC void soft_process(struct motorRecord *); + + +struct motor_dset devMotorSoft = +{ + {8, NULL, soft_init, soft_init_record, NULL}, + update, + start, + build, + end +}; + + +STATIC long update(struct motorRecord *mr) +{ + struct soft_private *ptr = (struct soft_private *) mr->dpvt; + + mr->msta = ptr->dinp_value ? RA_DONE : 0; + return(ptr->callback_flag); +} + + +STATIC long start(struct motorRecord *mr) +{ + return((long) OK); +} + + +STATIC long end(struct motorRecord *mr) +{ + struct soft_private *ptr = (struct soft_private *) mr->dpvt; + + if (ptr->default_done_behavior == YES) + mr->msta = RA_DONE; + return((long) OK); +} + + +STATIC long build(motor_cmnd command, double *parms, struct motorRecord *mr) +{ + const short int stop = 1; + long status; + + switch (command) + { + case MOVE_ABS: + case MOVE_REL: + status = dbPutLink(&mr->out, DBR_DOUBLE, &mr->dval, 1); + break; + + case STOP_AXIS: + status = dbPutLink(&mr->stoo, DBR_SHORT, &stop, 1); + break; + + case SET_HIGH_LIMIT: + case SET_LOW_LIMIT: + status = OK; + break; + + default: + status = ERROR; + } + return(status); +} + + +/* +FUNCTION... void soft_dinp_func(struct motorRecord *, short) +USAGE... Update soft channel device input links and + process soft channel motor record when done moving. +LOGIC... + Get DINP_VALUE via DINP link. + IF Get() succeeds and DINP_VALUE is true + Process soft channel record + ENDIF +*/ +void soft_dinp_func(struct motorRecord *mr, short newdinp) +{ + struct soft_private *ptr = (struct soft_private *) mr->dpvt; + + ptr->dinp_value = newdinp; + if (ptr->dinp_value) + soft_process(mr); +} + + +void soft_rinp_func(struct motorRecord *mr, long newrinp) +{ + mr->rmp = newrinp; + soft_process(mr); +} + + +void soft_rdbl_func(struct motorRecord *mr, double newrdbl) +{ + newrdbl = newrdbl / mr->res; + if ((mr->msta & EA_PRESENT) && mr->ueip) + mr->rep = NINT(newrdbl); + else + mr->rmp = NINT(newrdbl); + soft_process(mr); +} + + +/* +FUNCTION... STATIC void soft_process(struct motorRecord *) +USAGE... Process the soft channel motor record. +LOGIC... + Lock soft channel record - call dbScanLock(). + Set call back flag to CALLBACK_DATA so readback will get updated. + Process soft channel record - call process(). + Unlock soft channel record - call dbScanUnlock(). +*/ + +STATIC void soft_process(struct motorRecord *mr) +{ + struct soft_private *ptr = (struct soft_private *) mr->dpvt; + struct rset *prset = (struct rset *) (mr->rset); + + dbScanLock((struct dbCommon *) mr); + ptr->callback_flag = CALLBACK_DATA; + (*prset->process) (mr); /* Process the soft channel record. */ + ptr->callback_flag = NOTHING_DONE; + dbScanUnlock((struct dbCommon *) mr); +} + diff --git a/motorApp/SoftMotorSrc/devSoft.h b/motorApp/SoftMotorSrc/devSoft.h new file mode 100644 index 00000000..2575917c --- /dev/null +++ b/motorApp/SoftMotorSrc/devSoft.h @@ -0,0 +1,51 @@ +/* +FILENAME.. devSoft.h + +USAGE... This file contains information that is common to + all Soft channel device support modules. + +Version: $Revision: 1.1 $ +Modified By: $Author: sluiter $ +Last Modified: $Date: 2000-02-08 22:19:26 $ +*/ + +/* + * Original Author: Ron Sluiter + * Date: 03/25/99 + * + * Experimental Physics and Industrial Control System (EPICS) + * + * Copyright 1991, the University of Chicago Board of Governors. + * + * This software was produced under U.S. Government contract + * W-31-109-ENG-38 at Argonne National Laboratory. + * + * Beamline Controls & Data Acquisition Group + * Experimental Facilities Division + * Advanced Photon Source + * Argonne National Laboratory + * + * Modification Log: + * ----------------- + */ + +#ifndef INCdevSofth +#define INCdevSofth 1 + +extern long soft_init(int); +extern long soft_init_record(struct motorRecord *); +extern void soft_dinp_func(struct motorRecord *, short); +extern void soft_rdbl_func(struct motorRecord *, double); +extern void soft_rinp_func(struct motorRecord *, long); + +#define MAXMSGS 20 + +struct soft_private +{ + long callback_flag; + short dinp_value; /* Value from DINP link. */ + BOOLEAN default_done_behavior; /* If the DINP is not programmed, exhibit + * "immediate done" default behavior. */ +}; + +#endif /* INCdevSofth */ diff --git a/motorApp/SoftMotorSrc/devSoftAux.c b/motorApp/SoftMotorSrc/devSoftAux.c new file mode 100644 index 00000000..e7af9181 --- /dev/null +++ b/motorApp/SoftMotorSrc/devSoftAux.c @@ -0,0 +1,152 @@ +/* +FILENAME... devSoftAux.c +USAGE... Motor record device level support for Soft channel. + +Version: $Revision: 1.1 $ +Modified By: $Author: sluiter $ +Last Modified: $Date: 2000-02-08 22:19:26 $ +*/ + +/* + * Original Author: Ron Sluiter + * Date: 06/15/99 + * + * Experimental Physics and Industrial Control System (EPICS) + * + * Copyright 1991, the University of Chicago Board of Governors. + * + * This software was produced under U.S. Government contract + * W-31-109-ENG-38 at Argonne National Laboratory. + * + * Beamline Controls & Data Acquisition Group + * Experimental Facilities Division + * Advanced Photon Source + * Argonne National Laboratory + * + * Modification Log: + * ----------------- + */ + + +/* +Notes: +- A Channel Access (CA) oriented file had to be created separate from the +primary devSoft.c file because CA and record access code cannot both reside +in the same file; each defines (redefines) the DBR's. +*/ + + +#include +#include +#include +#include + +#include "motorRecord.h" + +typedef enum BOOLEAN_VALUES {OFF = 0, ON = 1} BOOLEAN; +#include "devSoft.h" + +#define STATIC static + +STATIC void soft_dinp(struct event_handler_args); +STATIC void soft_rdbl(struct event_handler_args); +STATIC void soft_rinp(struct event_handler_args); +STATIC int soft_motor_task(int, int, int, int, int, int, int, int, int, int); +STATIC SEM_ID soft_motor_sem; +STATIC MSG_Q_ID soft_motor_msgQ; + +STATIC void soft_dinp(struct event_handler_args args) +{ + soft_dinp_func((struct motorRecord *) args.usr, *((short *) args.dbr)); +} + + +STATIC void soft_rdbl(struct event_handler_args args) +{ + soft_rdbl_func((struct motorRecord *) args.usr, *((double *) args.dbr)); +} + +STATIC void soft_rinp(struct event_handler_args args) +{ + soft_rinp_func((struct motorRecord *) args.usr, *((long *) args.dbr)); +} + +long soft_init(int after) +{ + if (after == 0) + { + soft_motor_sem = semBCreate(SEM_Q_PRIORITY, SEM_EMPTY); + soft_motor_msgQ = msgQCreate(MAXMSGS, 4, MSG_Q_FIFO); + taskSpawn((char *) "soft_motor", 64, VX_FP_TASK | VX_STDIO, 5000, + soft_motor_task, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + } + else + semGive(soft_motor_sem); /* Start soft_motor_task(). */ + return((long) OK); +} + + +long soft_init_record(struct motorRecord *mr) +{ + struct soft_private *ptr; + STATUS status; + static int count = 0; + + if (++count > MAXMSGS) + return(ERROR); + status = msgQSend(soft_motor_msgQ, (char *) &mr, 4, NO_WAIT, + MSG_PRI_NORMAL); + + /* Allocate space for private field. */ + mr->dpvt = (struct soft_private *) malloc(sizeof(struct soft_private)); + ptr = (struct soft_private *) mr->dpvt; + ptr->dinp_value = mr->dmov; /* Must match after initialzation. */ + + return ((long) status); +} + + +STATIC int soft_motor_task(int a1, int a2, int a3, int a4, int a5, int a6, + int a7, int a8, int a9, int a10) +{ + struct motorRecord *mr; + chid dinp, rdbl, rinp; + + semTake(soft_motor_sem, WAIT_FOREVER); /* Wait for dbLockInitRecords() to execute. */ + SEVCHK(ca_task_initialize(),"ca_task_initialize"); + + while (msgQReceive(soft_motor_msgQ, (char *) &mr, 4, NO_WAIT) != ERROR) + { + if (mr->dinp.value.constantStr == NULL) + { + struct soft_private *ptr = (struct soft_private *) mr->dpvt; + ptr->default_done_behavior = ON; + } + else + { + SEVCHK(ca_search(mr->dinp.value.pv_link.pvname, &dinp), + "ca_search() failure"); + SEVCHK(ca_add_event(DBR_SHORT, dinp, soft_dinp, mr, NULL),"ca_add_event() failure"); + SEVCHK(ca_pend_io((float) 5.0), "DINP link failure"); + } + + if (mr->urip != 0) + { + SEVCHK(ca_search(mr->rdbl.value.pv_link.pvname, &rdbl),"ca_search() failure"); + SEVCHK(ca_add_event(DBR_DOUBLE, rdbl, soft_rdbl, mr, NULL),"ca_add_event() failure"); + SEVCHK(ca_pend_io((float) 5.0), "RDBL link failure"); + } + + if (mr->rinp.value.constantStr != NULL) + { + SEVCHK(ca_search(mr->rinp.value.pv_link.pvname, &rinp),"ca_search() failure"); + SEVCHK(ca_add_event(DBR_LONG, rinp, soft_rinp, mr, NULL),"ca_add_event() failure"); + SEVCHK(ca_pend_io((float) 5.0), "RINP link failure"); + } + } + + msgQDelete(soft_motor_msgQ); + taskSuspend(NULL); /* Wait Forever. */ + return(ERROR); +} + diff --git a/motorApp/SoftMotorSrc/devSoftMotor.dbd b/motorApp/SoftMotorSrc/devSoftMotor.dbd new file mode 100644 index 00000000..45f91217 --- /dev/null +++ b/motorApp/SoftMotorSrc/devSoftMotor.dbd @@ -0,0 +1,3 @@ +# Soft Channel driver support. +device(motor,CONSTANT,devMotorSoft,"Soft Channel") + diff --git a/motorApp/V544Src/Makefile b/motorApp/V544Src/Makefile new file mode 100644 index 00000000..0380f4bf --- /dev/null +++ b/motorApp/V544Src/Makefile @@ -0,0 +1,3 @@ +TOP=../.. +include $(TOP)/config/CONFIG_APP +include $(TOP)/config/RULES_ARCHS diff --git a/motorApp/V544Src/Makefile.Host b/motorApp/V544Src/Makefile.Host new file mode 100644 index 00000000..830893f0 --- /dev/null +++ b/motorApp/V544Src/Makefile.Host @@ -0,0 +1,11 @@ +# Makefile.Host +TOP = ../../.. +include $(TOP)/config/CONFIG_APP +#---------------------------------------- +# ADD MACRO DEFINITIONS AFTER THIS LINE + +DBDINSTALL += devHighland.dbd + +include $(TOP)/config/RULES.Host +#---------------------------------------- +# ADD RULES AFTER THIS LINE diff --git a/motorApp/V544Src/Makefile.Vx b/motorApp/V544Src/Makefile.Vx new file mode 100644 index 00000000..8561c45a --- /dev/null +++ b/motorApp/V544Src/Makefile.Vx @@ -0,0 +1,22 @@ +# Makefile.Vx +TOP = ../../.. +include $(TOP)/config/CONFIG_APP +#---------------------------------------- +# ADD MACRO DEFINITIONS AFTER THIS LINE + +# The following are used for debugging messages. +#USR_CFLAGS += -DDEBUG +#USR_CXXFLAGS += -DDEBUG + +# The following is required for the Highland V544 (i.e., V544) device driver. +SRCS.c += ../devV544.c ../drvV544.c +LIBOBJS += devV544.o drvV544.o + +LIBNAME = V544Lib + +#Note that the command line that builds the +#library $(LIBNAME) may be HUGE (>3kB) +# +include $(TOP)/config/RULES.Vx +#---------------------------------------- +# ADD RULES AFTER THIS LINE diff --git a/motorApp/V544Src/devHighland.dbd b/motorApp/V544Src/devHighland.dbd new file mode 100644 index 00000000..edda7dcf --- /dev/null +++ b/motorApp/V544Src/devHighland.dbd @@ -0,0 +1,4 @@ +# Highland V544 driver support. +device(motor,VME_IO,devV544,"Highland V544") +driver(drvV544) + diff --git a/motorApp/V544Src/devV544.c b/motorApp/V544Src/devV544.c new file mode 100644 index 00000000..f2c52b4f --- /dev/null +++ b/motorApp/V544Src/devV544.c @@ -0,0 +1,688 @@ +/* File: devV544.c */ +/* Version: 1.2a */ +/* Date Last Modified: 2/19/97 */ + +/* Device Support Routines for motor */ +/* + * Original Author: Jim Kowalkowski + * Current Author: Joe Sullivan + * Date: 11/14/94 + * + * Experimental Physics and Industrial Control System (EPICS) + * + * Copyright 1991, the Regents of the University of California, + * and the University of Chicago Board of Governors. + * + * This software was produced under U.S. Government contracts: + * (W-7405-ENG-36) at the Los Alamos National Laboratory, + * and (W-31-109-ENG-38) at Argonne National Laboratory. + * + * Initial development by: + * The Controls and Automation Group (AT-8) + * Ground Test Accelerator + * Accelerator Technology Division + * Los Alamos National Laboratory + * + * Co-developed with + * The Controls and Computing Group + * Accelerator Systems Division + * Advanced Photon Source + * Argonne National Laboratory + * + * Modification Log: + * ----------------- + * .01 01-18-93 jbk initialized + * .02 11-14-94 jps copy devOms.c and modify to point to vme58 driver + * .03 ??-??-95 jps copy devOms58.c and modify to point to Highland driver + * .04 06-20-96 jps allow for bumpless-reboot on position + * .04a 02-19-97 tmm fixed for EPICS 3.13 + */ + +#define VERSION 1.3 + +#include +#include +#include +#include +#include /* jps: include for init_record wait */ +#include +#ifdef __cplusplus +extern "C" { +#include +#include +#include +} +#else +#include +#include +#include +#endif +#include +#include +#include +#include +#include + +#include "motorRecord.h" +#include "motor.h" +#include "drvV544.h" + +#define PRIVATE_FUNCTIONS 0 /* normal:1, debug:0 */ + +#define STATIC + +#ifdef NODEBUG +#define Debug(L,FMT,V) ; +#else +#define Debug(L,FMT,V) { if(L <= devV544debug) \ + { printf("%s(%d):",__FILE__,__LINE__); \ + printf(FMT,V); } } +#endif + +#define NINT(f) (long)((f)>0 ? (f)+0.5 : (f)-0.5) /* tmm */ + +/* ----------------Create the dsets for devV544----------------- */ +static long report(); +STATIC long v544_init(int after); +STATIC long v544_init_record(struct motorRecord *); +STATIC long get_ioint_info(); +STATIC long v544_start_trans(struct motorRecord *); +STATIC long v544_update_values(struct motorRecord *); +STATIC long v544_build_trans(motor_cmnd, double *, struct motorRecord *); +STATIC long v544_end_trans(struct motorRecord *); +STATIC void v544_callback(MOTOR_RETURN *); +STATIC void v544_init_callback(MOTOR_RETURN *); + +struct motor_dset devV544 = +{ + {8, NULL, v544_init, v544_init_record, NULL}, + v544_update_values, + v544_start_trans, + v544_build_trans, + v544_end_trans +}; + +/* xxDSET devXxxx={ 5,report,init,init_rec,get_ioint_info,read_write}; */ + +/* --------------------------- program data --------------------- */ + +volatile int devV544debug = 0; + + +struct v544_motor_table +{ + int type; + uint16_t command; + int num_parms; +}; + +static struct v544_motor_table v544_table[] = { +/* type cmd num_parms */ + {MOTION, CS_MOVE_ABS, 1}, /* MOVE_ABS */ + {MOTION, CS_MOVE_INC, 1}, /* MOVE_REL */ + {MOTION, CS_SEEK_INDX, 1}, /* HOME_FOR */ + {MOTION, CS_SEEK_INDX, 1}, /* HOME_REV */ + {IMMEDIATE, CS_SET_POSITION, 1}, /* LOAD_POS */ + {IMMEDIATE, -1, 1}, /* SET_VELO_BASE - Not supported */ + {IMMEDIATE, -1, 1}, /* SET_VELO */ + {IMMEDIATE, -1, 1}, /* SET_ACCEL */ + {IMMEDIATE, CS_GO, 0}, /* GO */ + {IMMEDIATE, -1, 2}, /* SET_ENC_RATIO - Not supported */ + {INFO, -1, 0}, /* GET_INFO */ + {MOVE_TERM, CS_IDLE, 0}, /* STOP_AXIS */ + {VELOCITY, -1, 1}, /* JOG */ + {UNDEFINED, 0, 0}, /* SET_PGAIN */ + {UNDEFINED, 0, 0}, /* SET_IGAIN */ + {UNDEFINED, 0, 0}, /* SET_DGAIN */ + {UNDEFINED, 0, 0}, /* ENABLE_TORQUE */ + {UNDEFINED, 0, 0}, /* DISABL_TORQUE */ + {UNDEFINED, 0, 0}, /* PRIMITIVE */ + {UNDEFINED, 0, 0}, /* SET_HIGH_LIMIT */ + {UNDEFINED, 0, 0} /* SET_LOW_LIMIT */ +}; + +/* status of an axis of a board, one present for each axis of each board*/ +struct a_axis +{ +/*??? int encoder_present; */ + int in_use; +}; + +/* the device private data structure of the record */ +struct v544_trans +{ + int state; + FAST_LOCK lock; + MOTOR_CALL motor_call; + int callback_changed; + int32_t motor_pos; + int32_t encoder_pos; + int32_t vel; + unsigned long status; + SEM_ID initSem; +}; + +/* the board status structure */ +struct v544_stat +{ + int exists; + int total_axis; + struct a_axis *v544_axis; +}; + +STATIC struct v544_stat *v544_cards; +extern struct v544_support v544_access; + +#define IDLE_STATE 0 +#define BUILD_STATE 1 +#define YES 1 +#define NO 0 + +#define SEM_TIMEOUT 50 + + +/* --------------------------- program data --------------------- */ + +/* initialize device support for V544 stepper motor */ +STATIC long v544_init(int after) +{ + MOTOR_CARD_QUERY card_query; + int i, j; + + Debug(1, "v544_init: entry...\n", 0); + if (after) + return (0); + + /* allocate space for total cards in system */ + v544_cards = (struct v544_stat *) malloc(V544_NUM_CARDS * + sizeof(struct v544_stat)); + + /* for each card ask driver for num of axis supported and name */ + for (i = 0; i < V544_NUM_CARDS; i++) + { + (*v544_access.get_card_info) (i, &card_query); + + if (card_query.total_axis == 0) + v544_cards[i].exists = NO; + else + { + v544_cards[i].exists = YES; + v544_cards[i].total_axis = card_query.total_axis; + Debug(5, "v544_init: calling malloc(card_query...)\n total axis =%d\n", + v544_cards[i].total_axis); + v544_cards[i].v544_axis = (struct a_axis *) malloc( + card_query.total_axis * sizeof(struct a_axis)); + + for (j = 0; j < card_query.total_axis; j++) + v544_cards[i].v544_axis[j].in_use = 0; + } + } + + Debug(1, "v544_init: exit...\n", 0); + return (0); +} + +/* initialize a record instance */ +STATIC long v544_init_record(struct motorRecord * mr) +{ + int card, signal; + int initEncoder, initPos; + struct v544_trans *ptrans; + MOTOR_AXIS_QUERY axis_query; + MOTOR_CALL *motor_call; + double ep_mp[2]; /* encoder pulses, motor pulses */ + int rtnStat; + + Debug(1, "v544_init_record: entry...\n", 0); + + /* allocate space for private field - an v544_trans structure */ + Debug(5, "v544_init_record: calling malloc(...v544_trans...)\n", 0); + mr->dpvt = (struct v544_trans *) malloc(sizeof(struct v544_trans)); + + ptrans = mr->dpvt; + ptrans->state = IDLE_STATE; + ptrans->callback_changed = NO; + FASTLOCKINIT(&ptrans->lock); + motor_call = &(ptrans->motor_call); + + callbackSetCallback((void (*)(struct callbackPvt *)) v544_callback, + &(motor_call->callback)); + callbackSetPriority(priorityMedium, &(motor_call->callback)); + + /* check if axis already in use, then mark card, axis as in use */ + + /* out must be an VME_IO */ + switch (mr->out.type) + { + case (VME_IO): + break; + default: + recGblRecordError(S_dev_badBus, (void *) mr, + (char *) "devV544 (init_record) Illegal OUT Bus Type"); + return (S_dev_badBus); + } + + card = mr->out.value.vmeio.card; + Debug(5, "v544_init_record: card %d\n", card); + signal = mr->out.value.vmeio.signal; + Debug(5, "v544_init_record: signal %d\n", signal); + + rtnStat = 0; + + if (card < 0 || card >= V544_NUM_CARDS || v544_cards[card].exists == NO) + { + recGblRecordError(S_db_badField, (void *) mr, + (char *) "devV544 (init_record) card does not exist!"); + rtnStat = S_db_badField; + } + else if (signal < 0 || signal >= v544_cards[card].total_axis) + { + recGblRecordError(S_db_badField, (void *) mr, + (char *) "devV544 (init_record) signal does not exist!"); + rtnStat = S_db_badField; + } + else if (v544_cards[card].v544_axis[signal].in_use == YES) + { + recGblRecordError(S_db_badField, (void *) mr, + "(char *) devV544 (init_record) motor already in use!"); + rtnStat = S_db_badField; + } + + if (rtnStat) + { + /* Initialize readback fields for simulation */ + mr->msta = RA_PROBLEM; + mr->res = mr->mres; + mr->rmp = 0; /* raw motor pulse count */ + mr->rep = 0; /* raw encoder pulse count */ + return (rtnStat); + } + else + /* query motor for all info to fill into record */ + (*v544_access.get_axis_info) (card, signal, &axis_query); + + mr->msta = axis_query.status; /* status info */ + + v544_cards[card].v544_axis[signal].in_use = YES; + + +/*jps: setting the encoder ratio was moved from init_record() remove callbacks during iocInit */ + /* + * Set the encoder ratio. Note this is blatantly device dependent. + * Determine the number of encoder pulses and the number of motor pulses + * per engineering unit (EGU). Send an array containing this information + * to device support. + */ + if ((initEncoder = (mr->msta & EA_PRESENT)) && mr->ueip) + { + if (fabs(mr->mres) < 1.e-9) + mr->mres = 1.; + if (fabs(mr->eres) < 1.e-9) + mr->eres = mr->mres; + { + int m; + for (m=10000000; (m > 1) && + (fabs(m/mr->eres) > 1.e6 || fabs(m/mr->mres) > 1.e6); + m /= 10) + ; + Debug(5, "oms58_init_record: ER mult = %d\n", m); + ep_mp[0] = m / mr->eres; /* encoder pulses per ...*/ + ep_mp[1] = m / mr->mres; /* motor pulses */ + } + mr->res = mr->eres; + } + else + { + ep_mp[0] = 1.; + ep_mp[1] = 1.; + mr->res = mr->mres; + } + + + /* Program the device if an initial position is programmed or + * an encoder is present. */ + + if ((initPos = (mr->dval && mr->res)) || initEncoder) + { + + /* + * Semaphore used to hold initialization until device is programmed - + * cleared by callback + */ + ptrans->initSem = semBCreate(SEM_Q_FIFO, SEM_EMPTY); + + /* + * Switch to special init callback so that record will not be + * processed during iocInit + */ + callbackSetCallback((void (*)(struct callbackPvt *)) v544_init_callback, + &(motor_call->callback)); + + if (initEncoder) + { + Debug(7, "v544_init_record: encoder pulses per EGU = %f\n", ep_mp[0]); + Debug(7, "v544_init_record: motor pulses per EGU = %f\n", ep_mp[1]); + + + v544_start_trans(mr); + v544_build_trans(SET_ENC_RATIO, ep_mp, mr); + v544_end_trans(mr); + } + + if (initPos) + { + double setPos = mr->dval / mr->res; + + Debug(7, "v544_init_record: initial raw position = %f\n", setPos); + + v544_start_trans(mr); + v544_build_trans(LOAD_POS, &setPos, mr); + v544_end_trans(mr); + } + + /* Changing positions or encoder ratio may have + * changed the readback value. */ + v544_start_trans(mr); + v544_build_trans(GET_INFO, NULL, mr); + v544_end_trans(mr); + + /* Wait for callback w/timeout */ + if ((rtnStat = semTake(ptrans->initSem, SEM_TIMEOUT)) == ERROR) + { + recGblRecordError(S_dev_NoInit, (void *) mr, + (char *) "dev_NoInit (init_record: callback2 timeout"); + } + semDelete(ptrans->initSem); + + /* Restore regular record callback */ + callbackSetCallback((void (*)(struct callbackPvt *)) v544_callback, + &(motor_call->callback)); + } + + + /* query motor for all info to fill into record */ + + (*v544_access.get_axis_info) (card, signal, &axis_query); + + mr->rmp = axis_query.position; /* raw motor pulse count */ + mr->rep = axis_query.encoder_position; /* raw encoder pulse count */ + mr->msta = axis_query.status; /* status info */ + Debug(7, "v544_init_record: rmp = %f\n", mr->rmp); + Debug(7, "v544_init_record: rep = %f\n", mr->rep); + Debug(7, "v544_init_record: msta = %d\n", mr->msta); + + Debug(1, "v544_init_record: exit...\n", 0); + return (0); +} + + +STATIC long v544_update_values(struct motorRecord * mr) +{ + struct v544_trans *ptrans; + long rc; + + rc = NOTHING_DONE; + ptrans = (struct v544_trans *) mr->dpvt; + + FASTLOCK(&ptrans->lock); + + /* raw motor pulse count */ + if (ptrans->callback_changed == YES) + { + mr->rmp = ptrans->motor_pos; + mr->rep = ptrans->encoder_pos; + mr->rvel = ptrans->vel; + mr->msta = ptrans->status; + ptrans->callback_changed = NO; + rc = CALLBACK_DATA; + } + + FASTUNLOCK(&ptrans->lock); + + return (rc); +} + + +/* start building a transaction */ +STATIC long v544_start_trans(struct motorRecord * mr) +{ + int card = mr->out.value.vmeio.card; + int axis = mr->out.value.vmeio.signal; + struct v544_trans *trans = (struct v544_trans *) mr->dpvt; + MOTOR_CALL *motor_call; + CMND_DEF *motor_cmnd; + + if (!mr->dpvt) + return (S_dev_NoInit); + + motor_call = &(trans->motor_call); + + /* + * initialize area to device private field to store command that is to be + * built and mark as command build in progress + */ + + trans->state = BUILD_STATE; + motor_call->card = card; + motor_call->signal = axis; + motor_call->type = UNDEFINED; + motor_call->precord = (struct dbCommon *) mr; + motor_call->cmndCnt = 0; + motor_call->cmndIndx = 0; + + /* Zero command define structure */ + bzero((void *)motor_call->cmndList, sizeof(CMND_DEF)*MAX_LIST_SIZE); + + Debug(3, "v544_start_trans: motor %d command started\n", (card * V544_NUM_CHANNELS) + axis); + return (0); +} + +/* end building a transaction */ +STATIC long v544_end_trans(struct motorRecord * mr) +{ + struct v544_trans *trans = (struct v544_trans *) mr->dpvt; + MOTOR_CALL *motor_call; + long rc; + int card = mr->out.value.vmeio.card; + int axis = mr->out.value.vmeio.signal; + + + rc = 0; + motor_call = &(trans->motor_call); + + switch (trans->state) + { + case BUILD_STATE: + /* shut off command build in process thing */ + trans->state = IDLE_STATE; + + Debug(3, "v544_end_trans: motor %d command send\n", (card * V544_NUM_CHANNELS) + axis); + + rc = (*v544_access.send) (motor_call); + break; + + case IDLE_STATE: + default: + rc = ERROR; + } + + return (rc); +} + +/* add a part to the transaction */ +STATIC long v544_build_trans(motor_cmnd command, double *parms, struct motorRecord * mr) +{ + struct v544_trans *trans = (struct v544_trans *) mr->dpvt; + MOTOR_CALL *motor_call; + char buffer[20]; + int parmInt; + int first_one, i; + + motor_call = &(trans->motor_call); + + if (v544_table[command].type == UNDEFINED) + return(OK); + + if (v544_table[command].type > motor_call->type) + motor_call->type = v544_table[command].type; + + if (devV544debug >= 4) + printf("build_trans: v544 cmndCode %d, cmndParm %12.4f\n", command, *parms); + + /* concatenate onto the dpvt message field */ + switch (trans->state) + { + case BUILD_STATE: + /* put in command */ + + switch (command) + { + case MOVE_ABS: + case MOVE_REL: + case HOME_FOR: + case HOME_REV: + motor_call->cmndList[motor_call->cmndCnt].cmnd |= v544_table[command].command; + motor_call->cmndList[motor_call->cmndCnt].pos = NINT(*parms); + break; + case LOAD_POS: + motor_call->cmndList[motor_call->cmndCnt].cmnd |= v544_table[command].command; + motor_call->cmndList[motor_call->cmndCnt].pos = NINT(*parms); + motor_call->cmndCnt++; + break; + case SET_VEL_BASE: + break; + case SET_VELOCITY: + parmInt = NINT(*parms/(double)VEL_SCALE); + motor_call->cmndList[motor_call->cmndCnt].vel = parmInt; + break; + case SET_ACCEL: + parmInt = NINT(*parms/(double)(ACC_SCALE*ACC_SCALE)); + if (parmInt == 0) + parmInt = 1; + motor_call->cmndList[motor_call->cmndCnt].accel = parmInt; + break; + case GO: + motor_call->cmndList[motor_call->cmndCnt].cmnd |= v544_table[command].command; + motor_call->cmndCnt++; + break; + case SET_ENC_RATIO: + case GET_INFO: + motor_call->cmndCnt++; + break; + case STOP_AXIS: + motor_call->cmndList[motor_call->cmndCnt].cmnd |= CS_IDLE; + motor_call->cmndList[motor_call->cmndCnt].vel = 0; + motor_call->cmndCnt++; + break; + case JOG: + if (*parms >= 0) + { + motor_call->cmndList[motor_call->cmndCnt].cmnd |= CS_RUN_CW; + + parmInt = NINT(*parms/(double)VEL_SCALE); + motor_call->cmndList[motor_call->cmndCnt].vel = parmInt; + } + else + { + motor_call->cmndList[motor_call->cmndCnt].cmnd |= CS_RUN_CCW; + parmInt = -1*NINT(*parms/(double)VEL_SCALE); + motor_call->cmndList[motor_call->cmndCnt].vel = parmInt; + } + motor_call->cmndCnt++; + break; + default: + break; + } + break; + case IDLE_STATE: + default: + return ERROR; + } + + return (0); +} + +/* callback from V544 driver for motor in motion */ +STATIC void v544_callback(MOTOR_RETURN * motor_return) +{ + struct motorRecord *mr = (struct motorRecord *) motor_return->precord; + struct rset *prset = (struct rset *) (motor_return->precord->rset); + struct v544_trans *ptrans; + + Debug(5, "v544_callback: entry\n", 0); + ptrans = (struct v544_trans *) mr->dpvt; + + Debug(6, "v544_callback: FASTLOCK\n", 0); + FASTLOCK(&ptrans->lock); + + /* raw motor pulse count */ + Debug(6, "v544_callback: update ptrans\n", 0); + ptrans->callback_changed = YES; + ptrans->motor_pos = motor_return->position; + /* raw encoder pulse count */ + ptrans->encoder_pos = motor_return->encoder_position; + /* raw motor velocity */ + ptrans->vel = motor_return->velocity*VEL_SCALE; + ptrans->status = motor_return->status; /* status */ + + if (devV544debug >= 4) + printf("v544 callback: pos %d, enc %d, vel %d, status =0x%04x\n", + ptrans->motor_pos, ptrans->encoder_pos,ptrans->vel, ptrans->status); + + Debug(6, "v544_callback: FASTUNLOCK\n", 0); + FASTUNLOCK(&ptrans->lock); + + /* free the return data buffer */ + Debug(6, "v544_callback: free MOTOR_RETURN\n", 0); + (*v544_access.free) (motor_return); + + Debug(6, "v544_callback: dbScanLock\n", 0); + dbScanLock((struct dbCommon *) mr); + Debug(6, "v544_callback: calling process\n", 0); + (*prset->process) ((struct dbCommon *) mr); + Debug(6, "v544_callback: dbScanUnlock\n", 0); + dbScanUnlock((struct dbCommon *) mr); + Debug(5, "v544_callback: exit..\n", 0); + return; +} + + +/* callback from V544 driver for init_record - duplicate of + * the v544_callback without the call to process the record */ +STATIC void v544_init_callback(MOTOR_RETURN * motor_return) +{ + struct motorRecord *mr = (struct motorRecord *) motor_return->precord; + struct rset *prset = (struct rset *) (motor_return->precord->rset); + struct v544_trans *ptrans; + + Debug(5, "v544_init_callback: entry\n", 0); + ptrans = (struct v544_trans *) mr->dpvt; + + Debug(6, "v544_init_callback: FASTLOCK\n", 0); + FASTLOCK(&ptrans->lock); + + /* raw motor pulse count */ + Debug(6, "v544_init_callback: update ptrans\n", 0); + ptrans->callback_changed = YES; + ptrans->motor_pos = motor_return->position; + /* raw encoder pulse count */ + ptrans->encoder_pos = motor_return->encoder_position; + /* raw motor velocity */ + ptrans->vel = motor_return->velocity; + + ptrans->status = motor_return->status; /* status */ + + /* free the return data buffer */ + Debug(6, "v544_init_callback: free MOTOR_RETURN\n", 0); + (*v544_access.free) (motor_return); + + /* Continue record init. */ + Debug(6, "v544_init_callback: Continue sig.\n", 0); + semGive(ptrans->initSem); + + Debug(6, "v544_init_callback: FASTUNLOCK\n", 0); + FASTUNLOCK(&ptrans->lock); + + Debug(5, "v544_init_callback: exit\n", 0); + return; +} diff --git a/motorApp/V544Src/drvV544.c b/motorApp/V544Src/drvV544.c new file mode 100644 index 00000000..1efbd2cd --- /dev/null +++ b/motorApp/V544Src/drvV544.c @@ -0,0 +1,1264 @@ +/* File: drvV544.c */ +/* Version: 1.3a */ +/* Date Last Modified: 2/19/97 */ + + +/* Device Driver Support routines for motor */ +/* + * Original Author: Jim Kowalkowski + * Current Author: Joe Sullivan + * Date: 11/14/94 + * + * Experimental Physics and Industrial Control System (EPICS) + * + * Copyright 1991, the Regents of the University of California, + * and the University of Chicago Board of Governors. + * + * This software was produced under U.S. Government contracts: + * (W-7405-ENG-36) at the Los Alamos National Laboratory, + * and (W-31-109-ENG-38) at Argonne National Laboratory. + * + * Initial development by: + * The Controls and Automation Group (AT-8) + * Ground Test Accelerator + * Accelerator Technology Division + * Los Alamos National Laboratory + * + * Co-developed with + * The Controls and Computing Group + * Accelerator Systems Division + * Advanced Photon Source + * Argonne National Laboratory + * + * Modification Log: + * ----------------- + * .01 01-18-93 jbk initialized (drvOms.c) + * .02 11-14-94 jps copy drvOms.c and modify to point to vme58 driver + * .03 ??-??-95 jps copy drvOms58.c and modify to point to Highland driver + * .03a 02-19-97 tmm fixed for EPICS 3.13 +*/ + +#include +#include +#include +#include +#include +#ifdef __cplusplus +extern "C" { +#include +#include +#include +} +#else +#include +#include +#include +#endif +#include +#include + +#include +/* #include tmm: EPICS 3.13 */ +#include +#include +#include +#include +#include +#include +#include + +#include "motor.h" +#include "drvV544.h" + +#define PRIVATE_FUNCTIONS 1 /* normal:1, debug:0 */ + +#define STATIC static + +/* Define for return test on locationProbe() */ +#define PROBE_SUCCESS(STATUS) ((STATUS)==S_dev_addressOverlap) + +#define SET_MM_ON(v,a) v|=(1< motor_scan_rate) + ? NO_WAIT : (motor_scan_rate - tick_curr); + wait_time = any_motor_in_motion ? (int) tick_count : WAIT_FOREVER; + + sem_ret = semTake(motor_sem, wait_time); + + if (sem_ret == ERROR) + tick_used = tickGet(); + + + if (any_motor_in_motion) + { + for (i = 0; i < v544_num_cards; i++) + { + if (motor_state[i] && + motor_state[i]->motor_in_motion) + query_axis(i); + } + } + + if (sem_ret != ERROR) + process_messages(); + } +} + + + +/*****************************************************/ +/* send query to get all axis in motions position */ +/* query_axis() */ +/*****************************************************/ +STATIC query_axis(int card) +{ + volatile struct vmex_motor *pmotor; + MOTOR_RETURN *mess_ret; + register struct mess_node *motor_motion; + register struct mess_info *motor_info; + int i; + + + pmotor = (struct vmex_motor *) motor_state[card]->localaddr; + + /* get the encoder positions for axis in motion */ + for (i = 0; i < motor_state[card]->total_axis; i++) + { + motor_info = &(motor_state[card]->motor_info[i]); + motor_motion = motor_info->motor_motion; + + /* + * Check for commanded motion and actual motion or done before + * issuing a callback. + */ + if (motor_motion && set_status(card, i)) + { + if (drvV544debug >= 4) + printf("query_axis: motor %d, pos=%d, enc=%d, vel=%d, status=0x%04x\n", + (card * V544_NUM_CHANNELS) + i, + motor_info->position, + motor_info->encoder_position, + motor_info->velocity, + motor_info->status); + + motor_motion->position = motor_info->position; + motor_motion->encoder_position = motor_info->encoder_position; + motor_motion->velocity = motor_info->velocity; + motor_motion->status = motor_info->status; + + mess_ret = (MOTOR_RETURN *) motor_malloc(); + mess_ret->callback = motor_motion->callback; + mess_ret->precord = motor_motion->precord; + mess_ret->position = motor_motion->position; + mess_ret->encoder_position = motor_motion->encoder_position; + mess_ret->velocity = motor_motion->velocity; + mess_ret->status = motor_motion->status; + mess_ret->type = motor_motion->type; + + if (motor_motion->status & RA_OVERTRAVEL || + motor_motion->status & RA_PROBLEM) + { + /* Assure motion is stopped */ + send_cmnd(card, i, 0, 0, 0, CS_IDLE); + Debug(1, "query_axis: motion aborted - status=0x%4x\n", + motor_info->status); + + motor_state[card]->motor_in_motion--; + + motor_free(motor_motion); + motor_motion = (struct mess_node *) NULL; + motor_info->motor_motion = (struct mess_node *) NULL; + } + else if (motor_motion->status & RA_DONE) + { + /* Check for additional motion in command list */ + if (motor_motion->cmndIndx < motor_motion->cmndCnt) + { + CMND_DEF *pcmnd = &motor_motion->cmndList[motor_motion->cmndIndx++]; + + send_cmnd(card, i, pcmnd->pos, pcmnd->vel, + pcmnd->accel, pcmnd->cmnd); + + /* Not done yet. */ + motor_motion->status &= ~RA_DONE; + mess_ret->status = motor_motion->status; + + } + else + { + motor_state[card]->motor_in_motion--; + + motor_free(motor_motion); + motor_motion = (struct mess_node *) NULL; + motor_info->motor_motion = (struct mess_node *) NULL; + } + } + + callbackRequest((CALLBACK *) mess_ret); + + if (motor_state[card]->motor_in_motion == 0) + { + SET_MM_OFF(any_motor_in_motion, card); + } + } + } + + return (0); +} + + +/************************************************************** + * Read motor status from card + * set_status() + ************************************************************/ + +STATIC set_status(int card, int signal) +{ + register struct mess_info *motor_info; + struct vmex_motor *pmotor; + MOTOR_REG *pmotorReg; + POS_REG *pmotorPos; + POS_REG motorPosReg; + int32_t motorPos; + uint16_t motorVel; + uint16_t motorStatusReg; + uint16_t motorCmndReg; + int rtn_state = 0; + + + motor_info = &(motor_state[card]->motor_info[signal]); + pmotor = (struct vmex_motor *) motor_state[card]->localaddr; + + pmotorReg = &pmotor->motorReg[signal]; + motorStatusReg = pmotorReg->status; + motorCmndReg = pmotorReg->cmnd; + + Debug(5, "set_status: cmndReg=0x%04x\n", motorCmndReg); + + + /* Test for command-in-progress */ + if (motorCmndReg & (CS_BUSY | CS_DONE)) + { + pmotorPos = &pmotor->posReg[signal]; + + /* Get motor position, test if read during update */ + do + { + motorPosReg.coarse = pmotorPos->coarse; + motorPosReg.fine = pmotorPos->fine; + } while (motorPosReg.coarse != pmotorPos->coarse); + + motorVel = pmotor->velReg[signal]; + motorPos = (motorPosReg.coarse << 16) | motorPosReg.fine; + + /* Set direction base on position history */ + if (motorPos > motor_info->position) + { + /* Positive motion - check clockwise limit */ + motor_info->status |= RA_DIRECTION; + + if ((motorStatusReg & STAT_CWL) && (motorCmndReg & CS_DONE)) + motor_info->status |= RA_OVERTRAVEL; + else + motor_info->status &= ~RA_OVERTRAVEL; + + motor_info->no_motion_count = 0; + } + else if (motorPos < motor_info->position) + { + /* Negative motion - check counterclockwise limit */ + motor_info->status &= ~RA_DIRECTION; + motorVel *= -1; + + if ((motorStatusReg & STAT_CCWL) && (motorCmndReg & CS_DONE)) + motor_info->status |= RA_OVERTRAVEL; + else + motor_info->status &= ~RA_OVERTRAVEL; + + motor_info->no_motion_count = 0; + } + else + { + /* + * No motion - check both limits and set direction accordingly. + */ + if (motorStatusReg & (STAT_CCWL | STAT_CWL)) + { + motor_info->status |= RA_OVERTRAVEL; + + if (motorStatusReg & STAT_CWL) + motor_info->status |= RA_DIRECTION; /* Positive Limit */ + else + motor_info->status &= ~RA_DIRECTION; /* Negative Limit */ + } + + motor_info->no_motion_count++; + + } + + if (motor_info->no_motion_count > motionTO) + { + motor_info->status |= RA_PROBLEM; + motor_info->no_motion_count = 0; + } + else + motor_info->status &= ~RA_PROBLEM; + + motor_info->encoder_position = 0; + motor_info->position = motorPos; + motor_info->velocity = motorVel; + + if (motorCmndReg & CS_DONE) + motor_info->status |= RA_DONE; + else + motor_info->status &= ~RA_DONE; + + if (motorStatusReg & STAT_INDX) + motor_info->status |= RA_HOME; + else + motor_info->status &= ~RA_HOME; + + rtn_state = (!motor_info->no_motion_count || + (motor_info->status & (RA_OVERTRAVEL | RA_DONE | RA_PROBLEM))) ? 1 : 0; + } + else if (motor_info->no_motion_count++ > motionTO) + { + /* Command was never acknowledged */ + motor_info->no_motion_count = 0; + + Debug(1, "set_status: Ack Timeout - cmndReg=0x%04x\n", motorCmndReg); + + motor_info->status |= RA_PROBLEM; + rtn_state = 1; + } + + return (rtn_state); +} + + +/*****************************************************/ +/* Process messages */ +/* process_messages() */ +/*****************************************************/ +STATIC process_messages() +{ + register struct mess_node *motor_motion; + struct mess_node *node; + CMND_DEF *pcmnd; + int16_t cmndbuf; + char qa_buf[15]; + + while (node = get_head_node()) + { + Debug(6, "Got node type (%d)\n", node->type); + + /* Check that card and signal exists - check tightened 6/11/96 (jps) */ + if ((node->card >= 0 && node->card < total_cards) && + motor_state[node->card] && + (node->signal >= 0 && node->signal < motor_state[node->card]->total_axis)) + { + switch (node->type) + { + case QUERY: + /* Not needed */ + callbackRequest((CALLBACK *) node); + break; + case VELOCITY: + /* send a message to V544 */ + motor_motion = + motor_state[node->card]->motor_info[node->signal].motor_motion; + + pcmnd = &node->cmndList[node->cmndIndx++]; + send_cmnd(node->card, node->signal, pcmnd->pos, pcmnd->vel, pcmnd->accel, + pcmnd->cmnd); + + /* + * this is tricky - another motion is here there is a very + * large assumption being made here: that the person who sent + * the previous motion is the same one that is sending this + * one, if he weren't, the guy that sent the original would + * never get notified of finish motion. This makes sense in + * record processing since only one record can be assigned to + * an axis and sent commands to it. An improvement would be + * to check and see if the record pointers were the same, if + * they were not, then send a finish message to the previous + * registered motion guy. + */ + + if (!motor_motion) /* if NULL */ + motor_state[node->card]->motor_in_motion++; + else + motor_free(motor_motion); + + SET_MM_ON(any_motor_in_motion, node->card); + motor_state[node->card]->motor_info[node->signal].motor_motion = node; + break; + case MOTION: + /* send a message to V544 */ + motor_motion = + motor_state[node->card]->motor_info[node->signal].motor_motion; + + pcmnd = &node->cmndList[node->cmndIndx++]; + send_cmnd(node->card, node->signal, pcmnd->pos, pcmnd->vel, pcmnd->accel, + pcmnd->cmnd); + + /* this is tricky - see velocity comment */ + if (!motor_motion) /* if NULL */ + motor_state[node->card]->motor_in_motion++; + else + motor_free(motor_motion); + + SET_MM_ON(any_motor_in_motion, node->card); + motor_state[node->card]->motor_info[node->signal].no_motion_count = 0; + motor_state[node->card]->motor_info[node->signal].motor_motion = node; + break; + case INFO: + set_status(node->card, node->signal); + node->position = + motor_state[node->card]->motor_info[node->signal].position; + node->encoder_position = + motor_state[node->card]->motor_info[node->signal].encoder_position; + node->status = + motor_state[node->card]->motor_info[node->signal].status; + + /* + * =========================================================== + * node->status & RA_DONE is not a reliable indicator of + * anything, in this case, since set_status() didn't ask the + * V544 controller to set the "Done" flag on command + * completion. Nevertheless, recMotor:process() needs to know + * whether the motor has stopped, and this we can tell by + * looking for a struct motor_motion. + * =========================================================== + * */ + motor_motion = + motor_state[node->card]->motor_info[node->signal].motor_motion; + if (motor_motion) + node->status &= ~RA_DONE; + else + node->status |= RA_DONE; + + callbackRequest((CALLBACK *) node); + break; + + case MOVE_TERM: + pcmnd = &node->cmndList[node->cmndIndx++]; + send_cmnd(node->card, node->signal, pcmnd->pos, pcmnd->vel, pcmnd->accel, + pcmnd->cmnd); + motor_motion = + motor_state[node->card]->motor_info[node->signal].motor_motion; + /* if motion-in-progress - cancel buffered commands */ + if (motor_motion) + motor_motion->cmndIndx = motor_motion->cmndCnt; + break; + default: + /* send a message to V544 */ + pcmnd = &node->cmndList[node->cmndIndx++]; + send_cmnd(node->card, node->signal, pcmnd->pos, pcmnd->vel, + pcmnd->accel, pcmnd->cmnd); + taskDelay(1); /* Give command time to finish */ + set_status(node->card, node->signal); + if (!motor_state[node->card]->motor_info[node->signal].status & CS_DONE) + Debug(1, "process(v455): motor %d Immediate command NOT-done yet.\n", + (node->card * V544_NUM_CHANNELS) + node->signal); + motor_free(node); /* free message buffer */ + break; + } + } + else + { + Debug(1, "send_mess - invalid card #%d\n", node->card); + node->position = 0; + node->encoder_position = 0; + node->velocity = 0; + node->status = RA_PROBLEM; + callbackRequest((CALLBACK *) node); + } + } + return (0); +} + + + + +/*****************************************************/ +/* Get a message off the queue */ +/* get_head_node() */ +/*****************************************************/ +STATIC struct mess_node *get_head_node() +{ + struct mess_node *node, *save_node; + + Debug(7, "get_head_node: entry\n", 0); + /* get a message from queue */ + FASTLOCK(&motor_queue); + + node = mess_queue.head; + save_node = (struct mess_node *) NULL; + Debug(7, "get_head_node: node = %x\n", node); + + /* delete node from list */ + if (node) + { /* tmm added */ + if (node == mess_queue.head) + { + Debug(7, "get_head_node: setting mess_queue.head=%x\n", node->next); + mess_queue.head = node->next; + } + if (node == mess_queue.tail) + { + Debug(7, "get_head_node: setting mess_queue.tail=%x\n", save_node); + mess_queue.tail = save_node; + } + if (save_node) + { + Debug(7, "get_head_node: setting save_node->next=%x\n", node->next); + save_node->next = node->next; + } + } + FASTUNLOCK(&motor_queue); + + Debug(7, "get_head_node: returning %x\n", node); + return (node); +} + + + + +/*****************************************************/ +/* send command to the V544 board control register */ +/* send_cmnd() */ +/* units = micro steps (256 ustep per step) */ +/*****************************************************/ +STATIC int send_cmnd(int card, /* board number */ + int motor,/* motor number */ + int32_t pos, /* request position */ + uint16_t vel, /* request velocity */ + uint16_t accel, /* request acceleration */ + uint16_t cmnd) /* motor command */ +{ + volatile struct vmex_motor *pmotor; /* board base address */ + volatile MOTOR_REG *pmotorReg; + int return_code, i, trys; + int abortTO; + uint16_t cmndSave; + + pmotor = (struct vmex_motor *) motor_state[card]->localaddr; + pmotorReg = &pmotor->motorReg[motor]; + return_code = 0; + + cmndSave = pmotorReg->cmnd; + + if (vel != 0 && !(cmndSave & CS_DONE)) + { + Debug(1, "send_cmnd: motor %d busy\n", (card * V544_NUM_CHANNELS) + motor); + return_code = -1; + } + + if (cmndSave & CS_ERROR) + { + Debug(1, "send_cmnd: command error - CMND=0x%04x\n", pmotorReg->cmnd); + return_code = -1; + } + + if ((pmotorReg->status) & STAT_MOTR) + { + Debug(1, "send_cmnd: motor %d sense error\n", (card * 4) + motor); + return_code = -1; + } + + pmotorReg->pos.coarse = pos >> 16; /* High word */ + pmotorReg->pos.fine = pos & 0xffff; /* Low word */ + pmotorReg->velocity = vel; + pmotorReg->accel = pmotorReg->lsDecel = accel; + + if (cmndSave & CS_DONE) + { + /* + * The v544 sometimes indicates done after 180us without starting the + * command. That time is well short of the normal latency so it is + * save to test for it. + */ + trys = 0; + do + { + pmotorReg->cmnd = (cmnd | CS_GO | v544InterruptMask | v544ParkSelect); + abortTO = v544CmndAbortTO; + while (abortTO--); /* dwell about 200us */ + if ((cmndSave = (pmotorReg->cmnd & CS_DONE))) + Debug(2, "send_cmnd: motor %d command aborted - retrying.\n", + (card * 4) + motor); + } while (cmndSave && trys++ < 3); + + if (drvV544sendTest) + { + POS_REG *posReg = &pmotor->posReg[motor]; + int posMoving = 0; + + pos = posReg->fine; + + printf("sendTest: Waiting for BUSY/DONE."); + while (!((cmndSave = pmotorReg->cmnd) & (CS_BUSY | CS_DONE))) + { + if (pos != posReg->fine && !posMoving) + { + posMoving = 1; + printf("\nMotor Moving"); + } + printf("."); + } + posMoving = 0; + if (cmndSave & CS_BUSY) + { + printf("\nsendTest: Waiting for DONE."); + while (!(pmotorReg->cmnd & CS_DONE)) + { + if (pos == posReg->fine && !posMoving) + { + posMoving = 1; + printf("\nMotor Stopped"); + } + pos = posReg->fine; + printf("."); + } + } + printf("\nsendTest: **DONE**\n"); + } + + if (drvV544debug >= 4) + printf("send_cmnd: motor %d, pos=%ld, vel=%d, accel=%d, cmnd=0x%04x\n", + (card * V544_NUM_CHANNELS) + motor, pos, vel, accel, + (cmnd | CS_GO | v544InterruptMask | v544ParkSelect)); + + + } + + return (return_code); +} + + + +/*****************************************************/ +/* Configuration function for module_types data */ +/* areas. v544Setup() */ +/*****************************************************/ +v544Setup(int num_cards, /* maximum number of cards in rack */ + int num_channels, /* Channels per card (4 - default) */ + void *addrs, /* Base Address(0xdd00 - default) */ + unsigned enableInt, /* 0=interrupts disabled, 1=VXI standard + * vector */ + int int_level, /* interrupt level (5 - default) */ + int scan_rate) /* polling rate - 1/60 sec units (6 - + * default) */ +{ + int vector; + + if (num_cards < 1 || num_cards > V544_NUM_CARDS) + v544_num_cards = V544_NUM_CARDS; + else + v544_num_cards = num_cards; + + if (num_channels < 1 || num_channels > V544_NUM_CHANNELS) + v544_num_channels = V544_NUM_CHANNELS; + else + v544_num_channels = num_channels; + + v544_addrs = addrs; + + if ((vector = enableInt) != 0) + { + vector = VXI_VECTOR((int) v544_addrs); + if (vector < 64 || vector > 255) + { + Debug(0, "v544Setup: invalid interrupt vector %d\n", vector); + Debug(0, " generated from VXI address 0x%x\n", addrs); + vector = 0; + } + } + + v544InterruptVector = vector; + + + if (int_level < 1 || int_level > 6) + { + Debug(0, "v544Setup: invalid interrupt level %d\n", int_level); + v544InterruptLevel = V544_INT_LEVEL; + } + else + v544InterruptLevel = int_level; + + /* Set motor polling task rate */ + if (scan_rate >= 1 && scan_rate <= sysClkRateGet()) + motor_scan_rate = sysClkRateGet() / scan_rate; + else + motor_scan_rate = SCAN_RATE; +} + + + +/*****************************************************/ +/* Interrupt service routine. */ +/* motorIsr() */ +/*****************************************************/ +STATIC void motorIsr(int card) +{ + volatile struct mot_state *pmotorState; + struct vmex_motor *pmotor; + + + if (card >= total_cards || (pmotorState = motor_state[card]) == NULL) + { + logMsg("Invalid entry-card #%d\n", card, 0, 0, 0, 0, 0); + return; + } + + pmotor = (struct vmex_motor *) (pmotorState->localaddr); + + if (drvV544debug >= 4) + logMsg("V544 card %d,vscr=0x%X\n", card, pmotor->vcsr, 0, 0, 0, 0); + + /* Let polling task check for motor done */ + semGive(motor_sem); +} + +STATIC int motorIsrSetup(unsigned card) +{ + struct vmex_motor *pmotor; + long status; + + Debug(6, "motorIsrSetup: Entry card#%d\n", card); + + pmotor = (struct vmex_motor *) (motor_state[card]->localaddr); + + status = devConnectInterrupt(V544_INTERRUPT_TYPE, + v544InterruptVector + card, + motorIsr, (void *) card); + if (!RTN_SUCCESS(status)) + { + errPrintf(status, __FILE__, __LINE__, "Can't connect to vector %d\n", + v544InterruptVector + card); + v544InterruptVector = 0;/* Disable interrupts */ + v544InterruptMask = CS_IEN_OFF; + return (ERROR); + } + + status = devEnableInterruptLevel(V544_INTERRUPT_TYPE, + v544InterruptLevel); + if (!RTN_SUCCESS(status)) + { + errPrintf(status, __FILE__, __LINE__, + "Can't enable enterrupt level %d\n", + v544InterruptLevel); + v544InterruptVector = 0;/* Disable interrupts */ + v544InterruptMask = CS_IEN_OFF; + return (ERROR); + } + + /* Setup mask for interrupt-on-done */ +/* v544InterruptMask = CS_IEN_ON; */ + return (OK); +} + +/*****************************************************/ +/* initialize all software and hardware */ +/* motor_init() */ +/*****************************************************/ +STATIC motor_init() +{ + volatile struct vmex_motor *pmotor; + long status; + int i, j, k; + char axis_pos[50]; + char encoder_pos[50]; + int total_encoders = 0; + int total_axis = 0; + void *localaddr; + void *probeAddr; + + /* Check for setup */ + if (v544_num_cards <= 0) + { + Debug(1, "motor_init: *V544 driver disabled* \n v544Setup() is missing from startup script.\n", 0); + return (ERROR); + } + + /* allocate space for total number of motors */ + (void *) motor_state = malloc(v544_num_cards * sizeof(struct mot_state *)); + + /* allocate structure space for each motor present */ + total_cards = 0; + + if (rebootHookAdd((FUNCPTR) v544_reset) == ERROR) + Debug(1, "vme58 motor_init: v544_reset disabled\n", 0); + + for (i = 0; i < v544_num_cards; i++) + { + Debug(2, "motor_init: v544 card %d\n", i); + + probeAddr = v544_addrs + (i * V544_BRD_SIZE); + Debug(9, "motor_init: locationProbe() on addr 0x%x\n", probeAddr); + status = locationProbe(V544_ADDRS_TYPE, probeAddr); + + if (PROBE_SUCCESS(status)) + { + status = devRegisterAddress(__FILE__, + V544_ADDRS_TYPE, + probeAddr, + V544_BRD_SIZE, + &localaddr); + Debug(9, "motor_init: v544 devRegisterAddress() status = %d\n", status); + if (!RTN_SUCCESS(status)) + { + errPrintf(status, __FILE__, __LINE__, + "Can't register V544 address 0x%x\n", probeAddr); + return (ERROR); + } + + Debug(9, "motor_init: V544 localaddr = %x\n", localaddr); + pmotor = (struct vmex_motor *) localaddr; + + total_cards++; + + Debug(9, "motor_init: V544 malloc'ing motor_state\n", 0); + motor_state[i] = (struct mot_state *) malloc( + sizeof(struct mot_state)); + motor_state[i]->localaddr = localaddr; + motor_state[i]->motor_in_motion = 0; + + /* Check controller ID */ + sprintf(motor_state[i]->ident, "%s rev. %d-%c, sn. %d", V544_LABEL, + pmotor->pgid, (char) pmotor->prev, pmotor->vsn); + Debug(3, "motor_init: V544 card ID = %s \n", motor_state[i]->ident); + + /* Issue warnings if self-test failed or VXI ID is not correct */ + if (!(pmotor->vcsr & (VCSR_SFT | VCSR_RDY))) + Debug(0, "motor_init: V544 card %d SELFTEST FAILED\n", i); + if (pmotor->vxid != V544_VXID) + Debug(0, "motor_init: V544 card %d - WRONG ID\n", i); + + total_axis = v544_num_channels; + + /* Test for optional encoders */ + if (pmotor->opts & OPTS_ENC) + total_encoders = total_axis; + else + total_encoders = 0; + + /* Initilize per-axis controller and internal data fields */ + for (j = 0; j < total_axis; j++) + { + send_cmnd(i, j, 0, 0, 0, CS_IDLE); + motor_state[i]->motor_info[j].motor_motion = NULL; + motor_state[i]->motor_info[j].status = 0; + if (total_encoders) + motor_state[i]->motor_info[j].encoder_present = YES; + else + motor_state[i]->motor_info[j].encoder_present = NO; + } + + Debug(3, "motor_init: Total axis = %d\n", total_axis); + + motor_state[i]->total_axis = total_axis; + + /* Enable interrupt-when-done if selected */ + if (v544InterruptVector) + { + if (motorIsrSetup(i) == ERROR) + errPrintf(0, __FILE__, __LINE__, "Interrupts Disabled!\n"); + } + + for (j = 0; j < total_axis; j++) + { + motor_state[i]->motor_info[j].status = 0; + motor_state[i]->motor_info[j].no_motion_count = 0; + motor_state[i]->motor_info[j].encoder_position = 0; + motor_state[i]->motor_info[j].position = 0; + + if (motor_state[i]->motor_info[j].encoder_present == YES) + motor_state[i]->motor_info[j].status |= EA_PRESENT; + + set_status(i, j); + } + + Debug(2, "motor_init: Init Address=0x%08.8x\n", localaddr); + Debug(3, "motor_init: Total encoders = %d\n\n", total_encoders); + } + else + { + Debug(3, "motor_init: Card NOT found!\n", 0); + motor_state[i] = (struct mot_state *) NULL; + } + } + + + motor_sem = semBCreate(SEM_Q_PRIORITY, SEM_EMPTY); + any_motor_in_motion = 0; + + + FASTLOCKINIT(&motor_queue); + FASTLOCK(&motor_queue); + mess_queue.head = (struct mess_node *) NULL; + mess_queue.tail = (struct mess_node *) NULL; + FASTUNLOCK(&motor_queue); + + FASTLOCKINIT(&motor_freelist); + FASTLOCK(&motor_freelist); + free_list.head = (struct mess_node *) NULL; + free_list.tail = (struct mess_node *) NULL; + FASTUNLOCK(&motor_freelist); + + Debug(3, "Motors initialized\n", 0); + + taskSpawn("tmotor", 64, VX_FP_TASK | VX_STDIO, 5000, motor_task, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + + Debug(3, "Started motor_task\n", 0); + + return (0); +} + + +/*****************************************************/ +/* send a command to the stepper motor task queue */ +/* motor_send() */ +/*****************************************************/ +STATIC int motor_send(MOTOR_CALL * u_msg) +{ + struct mess_node *new_message; + int index; + + Debug(9, "motor_send invoked for v544\n", 0); + + new_message = motor_malloc(); + new_message->callback = u_msg->callback; + new_message->next = (struct mess_node *) NULL; + new_message->type = u_msg->type; + new_message->signal = u_msg->signal; + new_message->card = u_msg->card; + new_message->precord = u_msg->precord; + new_message->status = 0; + new_message->cmndCnt = u_msg->cmndCnt; + new_message->cmndIndx = 0; + + bcopy((void *) u_msg->cmndList, (void *) new_message->cmndList, sizeof(CMND_DEF) * u_msg->cmndCnt); + + switch (new_message->type) + { + case MOTION: + break; + case VELOCITY: + break; + case MOVE_TERM: + break; + case IMMEDIATE: + break; + case QUERY: + case INFO: + break; + default: + return (-1); + break; + } + + FASTLOCK(&motor_queue); + + if (mess_queue.tail) + { + mess_queue.tail->next = new_message; + mess_queue.tail = new_message; + } + else + { + mess_queue.tail = new_message; + mess_queue.head = new_message; + } + + FASTUNLOCK(&motor_queue); + + semGive(motor_sem); + + return (0); +} + +STATIC struct mess_node *motor_malloc() +{ + struct mess_node *node; + + FASTLOCK(&motor_freelist); + + if (!free_list.head) + node = (struct mess_node *) malloc(sizeof(struct mess_node)); + else + { + node = free_list.head; + free_list.head = node->next; + if (!free_list.head) + free_list.tail = (struct mess_node *) NULL; + } + + FASTUNLOCK(&motor_freelist); + return (node); +} + +STATIC int motor_free(struct mess_node * node) +{ + FASTLOCK(&motor_freelist); + + node->next = (struct mess_node *) NULL; + + if (free_list.tail) + { + free_list.tail->next = node; + free_list.tail = node; + } + else + { + free_list.head = node; + free_list.tail = node; + } + + FASTUNLOCK(&motor_freelist); + return (0); +} + +/*---------------------------------------------------------------------*/ +/* + * both of these routines are only to be used at initialization time - before + * queuing transactions starts for an axis. These routines do no locking, + * since transactions will not enter the queue for an axis until after they + * have run. + */ + +/* return the card information to caller */ +STATIC int motor_card_info(int card, MOTOR_CARD_QUERY * cq) +{ + if (card >= 0 && + card < total_cards && + motor_state[card]) + { + cq->total_axis = motor_state[card]->total_axis; + cq->card_name = motor_state[card]->ident; + } + else + cq->total_axis = 0; + + return (0); +} + +/* return information for an axis to the caller */ +STATIC int motor_axis_info(int card, int signal, MOTOR_AXIS_QUERY * aq) +{ + if (card >= 0 && + card < total_cards && + motor_state[card] && + signal >= 0 && + signal < motor_state[card]->total_axis) + { + aq->position = motor_state[card]->motor_info[signal].position; + aq->encoder_position = + motor_state[card]->motor_info[signal].encoder_position; + aq->status = motor_state[card]->motor_info[signal].status; + } + else + { + aq->position = aq->encoder_position = 0; + aq->status = RA_PROBLEM; + } + + return (0); +} + +/* + * + * Disables interrupts. Called on CTL X reboot. + * + */ + +STATIC void v544_reset() +{ + short card; + short axis; + struct vmex_motor *pmotor; + short status; + + for (card = 0; card < total_cards; card++) + { + (void *) pmotor = motor_state[card]->localaddr; + if (vxMemProbe((char *) pmotor, READ, sizeof(short), (char *) &status) == OK) + { + v544InterruptMask = CS_IEN_OFF; + for (axis = 0; axis < v544_num_channels; axis++) + send_cmnd(card, axis, 0, 0, 0, CS_IDLE); + } + } +} + + +/*---------------------------------------------------------------------*/ diff --git a/motorApp/V544Src/drvV544.h b/motorApp/V544Src/drvV544.h new file mode 100644 index 00000000..fa67ed95 --- /dev/null +++ b/motorApp/V544Src/drvV544.h @@ -0,0 +1,266 @@ +/* File: drvV544.h */ +/* Version: 1.2 */ +/* Date Last Modified: 8/6/96 */ + + +/* Device Support Definitions for Highland motor controller */ +/* + * Original Author: Jim Kowalkowski + * Current Author: Joe Sullivan + * Date: 11/14/94 + * + * Experimental Physics and Industrial Control System (EPICS) + * + * Copyright 1991, the Regents of the University of California, + * and the University of Chicago Board of Governors. + * + * This software was produced under U.S. Government contracts: + * (W-7405-ENG-36) at the Los Alamos National Laboratory, + * and (W-31-109-ENG-38) at Argonne National Laboratory. + * + * Initial development by: + * The Controls and Automation Group (AT-8) + * Ground Test Accelerator + * Accelerator Technology Division + * Los Alamos National Laboratory + * + * Co-developed with + * The Controls and Computing Group + * Accelerator Systems Division + * Advanced Photon Source + * Argonne National Laboratory + * + * Modification Log: + * ----------------- + * .01 01-18-93 jbk initialized + * .02 11-14-94 jps copy drvOms.h and modify to point to vme58 driver + * .03 ??-??-95 jps copy drvOms58.h and modify to point to Highland driver + * ... + */ + + + + +/* + * valid command types for the driver, the order is importance, 0 is of + * lowest importance. Device support will choose the greatest one to use as + * the driver transaction type. + */ + +#define UNDEFINED (unsigned char)0 /* garbage type */ +#define IMMEDIATE (unsigned char)1 /* 'i' an execute immediate, no reply */ +#define MOVE_TERM (unsigned char)2 /* 't' terminate a previous active + * motion */ +#define MOTION (unsigned char)3 /* 'm' will produce motion updates */ +#define VELOCITY (unsigned char)4 /* 'v' make motion updates till + * MOVE_TERM */ +#define INFO (unsigned char)5 /* 'f' get curr motor/encoder pos and + * stat */ +#define QUERY (unsigned char)6 /* 'q' specialty type, not needed */ + +/* Set list size to accomidate the 2 move commands + * when all parameters are being changed */ +#define MAX_LIST_SIZE 10 + +typedef struct CmndDef +{ + int32_t pos; + uint16_t vel; + uint16_t accel; + uint16_t standby; /* Drive current standby percent */ + uint16_t cmnd; /* Motion command (ABS,INC, HOME,...) */ +} CMND_DEF; + +/* message queue management - device and driver support only */ +struct mess_node +{ + CALLBACK callback; + int signal; + int card; + unsigned char type; + int cmndCnt; /* Number of commands in list */ + int cmndIndx; /* Current command index */ + CMND_DEF cmndList[MAX_LIST_SIZE]; + long position; + long encoder_position; + long velocity; + unsigned long status; + struct dbCommon *precord; + struct mess_node *next; +}; +typedef struct mess_node MOTOR_CALL; +typedef struct mess_node MOTOR_RETURN; + +typedef struct mess_card_query +{ + char *card_name; + int total_axis; +} MOTOR_CARD_QUERY; + +typedef struct mess_axis_query +{ + long position; + long encoder_position; + unsigned long status; +} MOTOR_AXIS_QUERY; + +struct v544_support +{ + int (*send) (MOTOR_CALL *); + int (*free) (struct mess_node *); + int (*get_card_info) (int, MOTOR_CARD_QUERY *); + int (*get_axis_info) (int, int signal, MOTOR_AXIS_QUERY *); +}; + +/* Board control register structures */ + + +/* + * V544 default profile + */ + +#define V544_NUM_CARDS 8 +#define V544_NUM_CHANNELS 4 +#define V544_INTERRUPT_TYPE intVME +#define V544_ADDRS_TYPE atVMEA16 +#define V544_NUM_ADDRS 0xDD00 +#define V544_INT_LEVEL 5 /* default interrupt level (1-6) */ +#define V544_BRD_SIZE 0x80 /* card address boundary */ + +/* Board type info */ +#define V544_LABEL "V544" +#define V544_VXID 0xFEEE /* Manufacturer registered VXI ID */ + +#define BRD_OFFSET 0x80 +#define VXI_BASE 0xC000 +#define VXI_VECTOR(v) ((v-VXI_BASE)/0x40) + +/* V544 Parameter Scaling Constants */ +/* (assuming half step scaling by the record */ +#define STEP_SCALE 256 /* Position scaling per step */ +#define VEL_SCALE 16 /* Velocity scaling per step/sec */ +#define ACC_SCALE 256 /* Acceleration scaling per step/sec/sec */ + +/* V544 Parameter Limits */ +#define MAX_VEL 4096 +#define MAX_ACCEL 65535 +#define MAX_POS (2**31) + +/* V544 Control Register Definitions */ + +/* VCSR register status bits (board level status) */ +#define VCSR_RES 0x1 /* Soft Reset */ +#define VCSR_SFT 0x4 /* Self-test passed */ +#define VCSR_RDY 0x8 /* Ready to operate */ +#define VCSR_MOD 0x4000 /* Module ID 'off' */ + +/* VCSR register commands (board level commands) */ +#define VCSR_FRZ 0x8000 /* Board level freeze on motor positions updates */ + +/* OPTS (options) register status masks */ +#define OPTS_ENC 0x1 /* Encoder option enabled */ +#define OPTS_ACT 0x2 /* Encoder FPGA installed */ +#define OPTS_SER 0x4 /* Serial port enabled */ + +/* Per-axis Command/Status register definitions */ +/* status bits */ +#define CS_BUSY_TO 2 /* Maximum command ACK lantency in polling cycles */ +#define CS_ABORT_TO 1900 /* Dwell loop count for send_cmnd() check of abort bug */ +#define CS_ERROR 0x8000 /* Command error flag */ +#define CS_DONE 0x80 /* Command completed flag */ +#define CS_BUSY 0x40 /* Command accepted */ + +/* CMD codes (bits 0-4)*/ +#define CS_IDLE 0x0 /* Soft-reset - update status/position regs */ +#define CS_RUN_CW 0x1 /* Constant velocity - CW */ +#define CS_RUN_CCW 0x2 /* Constant velocity - CCW */ +#define CS_MOVE_ABS 0x3 /* Move to absolute motor position */ +#define CS_MOVE_INC 0x4 /* Move to relative motor position */ +#define CS_SEEK_INDX 0x5 /* Stop ABS move at index switch */ +#define CS_BACKOFF_CW 0x6 /* Stop CW ABS move when off limit switch */ +#define CS_BACKOFF_CCW 0x7 /* Stop CCW ABS move when off limit switch */ +#define CS_SET_ZERO 0x8 /* Set absolute zero to motor position */ +#define CS_SET_POSITION 0x9 /* Set logical position to motor position */ +#define CS_READ_THETA 0xA /* Read motor quadrant/microstep position */ + +/* Operations */ +#define CS_GO 0x20 /* Initiate command execution */ + +/* Interrupt control */ +#define CS_IEN_ON 0x4000 /* Bit 14 - Enable interrupt on "DONE" */ +#define CS_IEN_OFF 0x0 + +/* Motor PARKing currents (bits 9,10) */ +#define CS_PARK_100 0x000 /* Part motor at 100% current */ +#define CS_PARK_75 0x200 /* 75% */ +#define CS_PARK_50 0x400 /* 50% */ +#define CS_PARK_0 0x600 /* 0% */ + + +/* V544 STATus Register Bit Mapping */ +#define STAT_BADC 0x8000 /* Illegal command detected */ +#define STAT_SPRA 0x20 /* Spare limit switch status */ +#define STAT_MOTR 0x8 /* Motor driver error */ +#define STAT_CCWL 0x4 /* CCW limit switch status */ +#define STAT_CWL 0x2 /* CW limit switch status */ +#define STAT_INDX 0x1 /* INDEX switch status */ + + + + +/* V544 DUAL-PORT MEMORY MAP */ +/* Define byte offsets into axis control registers */ +#define COARSE_POS_ID 0x0 +#define FINE_POS_ID 0x2 +#define VELOCITY_ID 0x4 +#define ACCEL_ID 0x6 +#define LS_DECEL_ID 0x8 +#define AUX_REG_ID 0xa +#define STAT_REG_ID 0xc +#define CMND_REG_ID 0xe + +/* + * union posReg{ + * int32_t all; + * struct { + * int16_t high; + * int16_t low; + * } split; + * }; +*/ + +typedef struct pos_reg_str { + int16_t coarse; + uint16_t fine; + } POS_REG; + +/* Define register structure */ +typedef struct motor_reg_str { + POS_REG pos; + uint16_t velocity; + uint16_t accel; + uint16_t lsDecel; + uint16_t aux; + uint16_t status; + uint16_t cmnd; + } MOTOR_REG; + + +struct vmex_motor { + uint16_t vxid; + uint16_t vtype; + uint16_t vcsr; + uint16_t pgid; + uint16_t prev; + uint16_t vsn; + uint16_t opts; + uint16_t scan; + + /* Programming/Status Registers */ + MOTOR_REG motorReg[V544_NUM_CHANNELS]; + + /* Readback Registers */ + POS_REG posReg[V544_NUM_CHANNELS]; /* Actual positions */ + uint16_t velReg[V544_NUM_CHANNELS]; /* Actual velocity */ +}; + diff --git a/motorApp/op/adl/motorx_all.adl b/motorApp/op/adl/motorx_all.adl new file mode 100644 index 00000000..d2ee3525 --- /dev/null +++ b/motorApp/op/adl/motorx_all.adl @@ -0,0 +1,2273 @@ + +file { + name="/home/oxygen5/SLUITER/epics/3.13.1/iocoms/omsApp/op/adl/motorx_all.adl" + version=020305 +} +display { + object { + x=115 + y=72 + width=445 + height=735 + } + clr=6 + bclr=3 + cmap="" + gridSpacing=5 + gridOn=0 + snapToGrid=0 +} +"color map" { + ncolors=65 + colors { + ffffff, + ececec, + dadada, + c8c8c8, + bbbbbb, + aeaeae, + 9e9e9e, + 919191, + 858585, + 787878, + 696969, + 5a5a5a, + 464646, + 2d2d2d, + 000000, + 00d800, + 1ebb00, + 339900, + 2d7f00, + 216c00, + fd0000, + de1309, + be190b, + a01207, + 820400, + 5893ff, + 597ee1, + 4b6ec7, + 3a5eab, + 27548d, + fbf34a, + f9da3c, + eeb62b, + e19015, + cd6100, + ffb0ff, + d67fe2, + ae4ebc, + 8b1a96, + 610a75, + a4aaff, + 8793e2, + 6a73c1, + 4d52a4, + 343386, + c7bb6d, + b79d5c, + a47e3c, + 7d5627, + 58340f, + 99ffff, + 73dfff, + 4ea5f9, + 2a63e4, + 0a00b8, + ebf1b5, + d4db9d, + bbc187, + a6a462, + 8b8239, + 73ff6b, + 52da3b, + 3cb420, + 289315, + 1a7309, + } +} +text { + object { + x=336 + y=51 + width=98 + height=26 + } + "basic attribute" { + clr=54 + fill="outline" + } + "dynamic attribute" { + vis="if zero" + chan="$(P)$(M).DMOV" + } + textix="Moving" + align="horiz. centered" +} +text { + object { + x=337 + y=52 + width=98 + height=26 + } + "basic attribute" { + clr=54 + fill="outline" + } + "dynamic attribute" { + vis="if zero" + chan="$(P)$(M).DMOV" + } + textix="Moving" + align="horiz. centered" +} +text { + object { + x=335 + y=50 + width=98 + height=26 + } + "basic attribute" { + clr=50 + fill="outline" + } + "dynamic attribute" { + vis="if zero" + chan="$(P)$(M).DMOV" + } + textix="Moving" + align="horiz. centered" +} +rectangle { + object { + x=1 + y=421 + width=110 + height=22 + } + "basic attribute" { + clr=54 + } +} +text { + object { + x=0 + y=420 + width=110 + height=20 + } + "basic attribute" { + clr=0 + fill="outline" + } + textix="Resolution" + align="horiz. centered" +} +"text entry" { + object { + x=345 + y=3 + width=90 + height=20 + } + control { + chan="$(P)$(M).EGU" + clr=14 + bclr=1 + } + limits { + } +} +rectangle { + object { + x=0 + y=222 + width=85 + height=22 + } + "basic attribute" { + clr=54 + } +} +text { + object { + x=0 + y=223 + width=85 + height=20 + } + "basic attribute" { + clr=0 + fill="outline" + } + textix="Dynamics" + align="horiz. centered" +} +rectangle { + object { + x=0 + y=30 + width=85 + height=22 + } + "basic attribute" { + clr=54 + } +} +text { + object { + x=0 + y=31 + width=80 + height=20 + } + "basic attribute" { + clr=0 + fill="outline" + } + textix="Drive" + align="horiz. centered" +} +text { + object { + x=285 + y=32 + width=30 + height=18 + } + "basic attribute" { + clr=20 + fill="outline" + } + "dynamic attribute" { + vis="if not zero" + chan="$(P)$(M).LLS" + } + textix="Limit" + align="horiz. centered" +} +rectangle { + object { + x=240 + y=189 + width=49 + height=24 + } + "basic attribute" { + clr=1 + fill="outline" + width=2 + } + "dynamic attribute" { + vis="if not zero" + chan="$(P)$(M).HOMF" + } +} +rectangle { + object { + x=189 + y=189 + width=49 + height=24 + } + "basic attribute" { + clr=1 + fill="outline" + width=2 + } + "dynamic attribute" { + vis="if not zero" + chan="$(P)$(M).HOMR" + } +} +"message button" { + object { + x=159 + y=193 + width=25 + height=22 + } + control { + chan="$(P)$(M).TWF" + clr=14 + bclr=51 + } + label=">" + press_msg="1" +} +"message button" { + object { + x=77 + y=193 + width=25 + height=22 + } + control { + chan="$(P)$(M).TWR" + clr=14 + bclr=51 + } + label="<" + press_msg="1" +} +"text entry" { + object { + x=103 + y=193 + width=56 + height=22 + } + control { + chan="$(P)$(M).TWV" + clr=14 + bclr=51 + } + limits { + } +} +"message button" { + object { + x=192 + y=166 + width=45 + height=20 + } + control { + chan="$(P)$(M).JOGR" + clr=14 + bclr=51 + } + label="JogR" + press_msg="1" + release_msg="0" +} +"message button" { + object { + x=242 + y=166 + width=45 + height=20 + } + control { + chan="$(P)$(M).JOGF" + clr=14 + bclr=51 + } + label="JogF" + press_msg="1" + release_msg="0" +} +"text entry" { + object { + x=83 + y=166 + width=100 + height=25 + } + control { + chan="$(P)$(M).RLV" + clr=14 + bclr=51 + } + limits { + } +} +"message button" { + object { + x=242 + y=191 + width=45 + height=20 + } + control { + chan="$(P)$(M).HOMF" + clr=14 + bclr=51 + } + label="HomF" + press_msg="1" +} +"message button" { + object { + x=191 + y=191 + width=45 + height=20 + } + control { + chan="$(P)$(M).HOMR" + clr=14 + bclr=51 + } + label="HomR" + press_msg="1" +} +text { + object { + x=83 + y=32 + width=100 + height=20 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="User" + align="horiz. centered" +} +text { + object { + x=187 + y=32 + width=100 + height=20 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Dial" + align="horiz. centered" +} +text { + object { + x=285 + y=32 + width=30 + height=18 + } + "basic attribute" { + clr=20 + fill="outline" + } + "dynamic attribute" { + vis="if not zero" + chan="$(P)$(M).HLS" + } + textix="Limit" + align="horiz. centered" +} +"text update" { + object { + x=83 + y=78 + width=100 + height=18 + } + monitor { + chan="$(P)$(M).RBV" + clr=54 + bclr=3 + } + limits { + } +} +"text update" { + object { + x=187 + y=78 + width=100 + height=18 + } + monitor { + chan="$(P)$(M).DRBV" + clr=54 + bclr=3 + } + limits { + } +} +"text entry" { + object { + x=83 + y=102 + width=100 + height=25 + } + control { + chan="$(P)$(M).VAL" + clr=14 + bclr=51 + } + limits { + } +} +"text entry" { + object { + x=83 + y=132 + width=100 + height=22 + } + control { + chan="$(P)$(M).LLM" + clr=14 + bclr=51 + } + limits { + } +} +"text entry" { + object { + x=187 + y=50 + width=100 + height=22 + } + control { + chan="$(P)$(M).DHLM" + clr=14 + bclr=51 + } + limits { + } +} +"text entry" { + object { + x=83 + y=50 + width=100 + height=22 + } + control { + chan="$(P)$(M).HLM" + clr=14 + bclr=51 + } + limits { + } +} +"text entry" { + object { + x=187 + y=102 + width=100 + height=25 + } + control { + chan="$(P)$(M).DVAL" + clr=14 + bclr=51 + } + limits { + } +} +"text entry" { + object { + x=187 + y=132 + width=100 + height=22 + } + control { + chan="$(P)$(M).DLLM" + clr=14 + bclr=51 + } + limits { + } +} +oval { + object { + x=290 + y=50 + width=21 + height=21 + } + "basic attribute" { + clr=20 + } + "dynamic attribute" { + vis="if not zero" + chan="$(P)$(M).HLS" + } +} +oval { + object { + x=290 + y=132 + width=21 + height=21 + } + "basic attribute" { + clr=20 + } + "dynamic attribute" { + vis="if not zero" + chan="$(P)$(M).LLS" + } +} +text { + object { + x=83 + y=153 + width=204 + height=13 + } + "basic attribute" { + clr=30 + fill="outline" + } + "dynamic attribute" { + vis="if not zero" + chan="$(P)$(M).LVIO" + } + textix="Soft-Limit Violation" + align="horiz. centered" +} +composite { + object { + x=79 + y=75 + width=210 + height=22 + } + "composite name"="" + vis="static" + chan="$(P)$(M).SET" + children { + rectangle { + object { + x=81 + y=77 + width=206 + height=18 + } + "basic attribute" { + clr=30 + fill="outline" + width=2 + } + "dynamic attribute" { + vis="if not zero" + chan="$(P)$(M).SET" + } + } + rectangle { + object { + x=79 + y=75 + width=210 + height=22 + } + "basic attribute" { + clr=14 + fill="outline" + width=2 + } + "dynamic attribute" { + vis="if not zero" + chan="$(P)$(M).SET" + } + } + } +} +composite { + object { + x=5 + y=53 + width=75 + height=162 + } + "composite name"="" + vis="static" + chan="" + children { + text { + object { + x=5 + y=196 + width=75 + height=19 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Tweak" + } + text { + object { + x=5 + y=169 + width=75 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="MoveRel" + } + text { + object { + x=5 + y=135 + width=75 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Lo limit" + } + text { + object { + x=5 + y=105 + width=75 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="MoveAbs" + } + text { + object { + x=5 + y=78 + width=75 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Readback" + } + text { + object { + x=5 + y=53 + width=75 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Hi limit" + } + } +} +text { + object { + x=83 + y=226 + width=100 + height=20 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Normal" + align="horiz. centered" +} +text { + object { + x=186 + y=226 + width=100 + height=20 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Backlash" + align="horiz. centered" +} +text { + object { + x=340 + y=32 + width=73 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Raw" + align="horiz. centered" +} +"text update" { + object { + x=335 + y=77 + width=100 + height=19 + } + monitor { + chan="$(P)$(M).RRBV" + clr=54 + bclr=3 + } + limits { + } +} +"choice button" { + object { + x=340 + y=133 + width=90 + height=80 + } + control { + chan="$(P)$(M).SPMG" + clr=31 + bclr=20 + } +} +"choice button" { + object { + x=340 + y=213 + width=90 + height=40 + } + control { + chan="$(P)$(M)_able.VAL" + clr=14 + bclr=51 + } +} +rectangle { + object { + x=81 + y=100 + width=356 + height=29 + } + "basic attribute" { + clr=30 + width=1 + } + "dynamic attribute" { + vis="if not zero" + chan="$(P)$(M).LVIO" + } +} +"text entry" { + object { + x=335 + y=102 + width=100 + height=25 + } + control { + chan="$(P)$(M).RVAL" + clr=14 + bclr=51 + } + limits { + } +} +text { + object { + x=60 + y=93 + width=20 + height=13 + } + "basic attribute" { + clr=30 + fill="outline" + } + "dynamic attribute" { + vis="if not zero" + chan="$(P)$(M).SET" + } + textix="SET" + align="horiz. centered" +} +polyline { + object { + x=0 + y=222 + width=299 + height=2 + } + "basic attribute" { + clr=54 + fill="outline" + width=2 + } + points { + (1,223) + (298,223) + } +} +polyline { + object { + x=296 + y=221 + width=3 + height=196 + } + "basic attribute" { + clr=54 + fill="outline" + width=2 + } + points { + (298,222) + (297,416) + } +} +polyline { + object { + x=0 + y=692 + width=248 + height=3 + } + "basic attribute" { + clr=54 + fill="outline" + width=2 + } + points { + (1,694) + (247,693) + } +} +rectangle { + object { + x=0 + y=0 + width=438 + height=26 + } + "basic attribute" { + clr=1 + } +} +"text entry" { + object { + x=0 + y=0 + width=190 + height=27 + } + control { + chan="$(P)$(M).DESC" + clr=54 + bclr=1 + } + limits { + } +} +polyline { + object { + x=0 + y=27 + width=440 + height=3 + } + "basic attribute" { + clr=54 + fill="outline" + width=3 + } + points { + (1,28) + (438,28) + } +} +polyline { + object { + x=0 + y=415 + width=438 + height=2 + } + "basic attribute" { + clr=54 + fill="outline" + width=2 + } + points { + (1,416) + (437,416) + } +} +text { + object { + x=190 + y=0 + width=150 + height=14 + } + "basic attribute" { + clr=14 + } + textix="($(P)$(M))" + align="horiz. centered" +} +"text update" { + object { + x=190 + y=13 + width=150 + height=14 + } + monitor { + chan="$(P)$(M).DTYP" + clr=54 + bclr=1 + } + align="horiz. centered" + limits { + } +} +rectangle { + object { + x=4 + y=702 + width=150 + height=24 + } + "basic attribute" { + clr=14 + } +} +text { + object { + x=3 + y=701 + width=60 + height=22 + } + "basic attribute" { + clr=15 + } + textix="Scan" + align="horiz. centered" +} +text { + object { + x=1 + y=258 + width=73 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Maximum" +} +"text entry" { + object { + x=81 + y=255 + width=100 + height=25 + } + control { + chan="$(P)$(M).VMAX" + clr=14 + bclr=51 + } + limits { + } +} +text { + object { + x=3 + y=287 + width=73 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Speed" +} +text { + object { + x=3 + y=312 + width=80 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Base Speed" +} +text { + object { + x=3 + y=337 + width=73 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Accel." +} +text { + object { + x=3 + y=362 + width=177 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Backlash distance" +} +text { + object { + x=3 + y=387 + width=177 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Move Fraction" +} +text { + object { + x=3 + y=447 + width=130 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Motor resolution" +} +text { + object { + x=3 + y=472 + width=130 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Encoder res." +} +text { + object { + x=3 + y=497 + width=130 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Readback res." +} +text { + object { + x=3 + y=522 + width=130 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Retry deadband" +} +text { + object { + x=3 + y=547 + width=60 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Retries" +} +text { + object { + x=3 + y=572 + width=130 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Use Encoder" +} +text { + object { + x=3 + y=597 + width=130 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Use Readback" +} +text { + object { + x=3 + y=672 + width=130 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Mode" +} +text { + object { + x=3 + y=622 + width=130 + height=14 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Readback Delay (s)" +} +text { + object { + x=6 + y=647 + width=80 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="RBV inLink" +} +rectangle { + object { + x=296 + y=297 + width=120 + height=22 + } + "basic attribute" { + clr=54 + } +} +text { + object { + x=301 + y=299 + width=110 + height=20 + } + "basic attribute" { + clr=0 + fill="outline" + } + textix="Calibration" +} +rectangle { + object { + x=244 + y=418 + width=85 + height=22 + } + "basic attribute" { + clr=54 + } +} +text { + object { + x=244 + y=420 + width=85 + height=20 + } + "basic attribute" { + clr=0 + fill="outline" + } + textix="Status" + align="horiz. centered" +} +text { + object { + x=303 + y=359 + width=31 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Off" +} +text { + object { + x=303 + y=322 + width=31 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Cal" +} +"choice button" { + object { + x=334 + y=320 + width=100 + height=21 + } + control { + chan="$(P)$(M).SET" + clr=14 + bclr=51 + } + stacking="column" +} +"text entry" { + object { + x=334 + y=343 + width=100 + height=25 + } + control { + chan="$(P)$(M).OFF" + clr=14 + bclr=51 + } + limits { + } +} +composite { + object { + x=303 + y=394 + width=131 + height=21 + } + "composite name"="" + vis="static" + chan="" + children { + text { + object { + x=303 + y=397 + width=31 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Dir" + } + "choice button" { + object { + x=334 + y=394 + width=100 + height=21 + } + control { + chan="$(P)$(M).DIR" + clr=14 + bclr=51 + } + stacking="column" + } + } +} +"text entry" { + object { + x=184 + y=334 + width=100 + height=25 + } + control { + chan="$(P)$(M).BACC" + clr=14 + bclr=51 + } + limits { + } +} +"text entry" { + object { + x=81 + y=334 + width=100 + height=25 + } + control { + chan="$(P)$(M).ACCL" + clr=14 + bclr=51 + } + limits { + } +} +"text entry" { + object { + x=184 + y=359 + width=100 + height=25 + } + control { + chan="$(P)$(M).BDST" + clr=14 + bclr=51 + } + limits { + } +} +"text entry" { + object { + x=184 + y=384 + width=100 + height=25 + } + control { + chan="$(P)$(M).FRAC" + clr=14 + bclr=51 + } + limits { + } +} +"text entry" { + object { + x=81 + y=284 + width=100 + height=25 + } + control { + chan="$(P)$(M).VELO" + clr=14 + bclr=51 + } + limits { + } +} +"text entry" { + object { + x=184 + y=284 + width=100 + height=25 + } + control { + chan="$(P)$(M).BVEL" + clr=14 + bclr=51 + } + limits { + } +} +"text entry" { + object { + x=134 + y=309 + width=100 + height=25 + } + control { + chan="$(P)$(M).VBAS" + clr=14 + bclr=51 + } + limits { + } +} +"text entry" { + object { + x=138 + y=444 + width=100 + height=25 + } + control { + chan="$(P)$(M).MRES" + clr=14 + bclr=51 + } + limits { + } +} +"text entry" { + object { + x=138 + y=469 + width=100 + height=25 + } + control { + chan="$(P)$(M).ERES" + clr=14 + bclr=51 + } + limits { + } +} +menu { + object { + x=138 + y=669 + width=100 + height=20 + } + control { + chan="$(P)$(M).OMSL" + clr=14 + bclr=51 + } +} +"text entry" { + object { + x=138 + y=519 + width=100 + height=25 + } + control { + chan="$(P)$(M).RDBD" + clr=14 + bclr=51 + } + limits { + } +} +"text entry" { + object { + x=168 + y=544 + width=70 + height=25 + } + control { + chan="$(P)$(M).RTRY" + clr=14 + bclr=51 + } + limits { + } +} +text { + object { + x=129 + y=547 + width=44 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="max:" +} +"text update" { + object { + x=71 + y=547 + width=50 + height=18 + } + monitor { + chan="$(P)$(M).RCNT" + clr=54 + bclr=3 + } + limits { + } +} +text { + object { + x=253 + y=464 + width=73 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="CurrDir" +} +"text update" { + object { + x=331 + y=464 + width=100 + height=18 + } + monitor { + chan="$(P)$(M).TDIR" + clr=54 + bclr=3 + } + limits { + } +} +text { + object { + x=253 + y=484 + width=73 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Moving" +} +"text update" { + object { + x=331 + y=484 + width=100 + height=18 + } + monitor { + chan="$(P)$(M).MOVN" + clr=54 + bclr=3 + } + limits { + } +} +"text update" { + object { + x=331 + y=444 + width=100 + height=18 + } + monitor { + chan="$(P)$(M).MSTA" + clr=54 + bclr=3 + } + format="hexadecimal" + limits { + } +} +text { + object { + x=253 + y=444 + width=73 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="State 0x" +} +text { + object { + x=253 + y=504 + width=73 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="At Home" +} +text { + object { + x=253 + y=544 + width=73 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Encoder" +} +text { + object { + x=253 + y=524 + width=73 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="MotorPos" +} +text { + object { + x=253 + y=564 + width=73 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="MIP 0x" +} +"text update" { + object { + x=331 + y=584 + width=100 + height=18 + } + monitor { + chan="$(P)$(M).DIFF" + clr=54 + bclr=3 + } + limits { + } +} +text { + object { + x=252 + y=584 + width=73 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Err" +} +"text update" { + object { + x=331 + y=504 + width=100 + height=18 + } + monitor { + chan="$(P)$(M).ATHM" + clr=54 + bclr=3 + } + limits { + } +} +"text update" { + object { + x=331 + y=544 + width=100 + height=18 + } + monitor { + chan="$(P)$(M).REP" + clr=54 + bclr=3 + } + limits { + } +} +"text update" { + object { + x=331 + y=524 + width=100 + height=18 + } + monitor { + chan="$(P)$(M).RMP" + clr=54 + bclr=3 + } + limits { + } +} +"text update" { + object { + x=331 + y=564 + width=100 + height=18 + } + monitor { + chan="$(P)$(M).MIP" + clr=54 + bclr=3 + } + format="hexadecimal" + limits { + } +} +"choice button" { + object { + x=138 + y=569 + width=100 + height=25 + } + control { + chan="$(P)$(M).UEIP" + clr=14 + bclr=51 + } + stacking="column" +} +"choice button" { + object { + x=138 + y=594 + width=100 + height=25 + } + control { + chan="$(P)$(M).URIP" + clr=14 + bclr=51 + } + stacking="column" +} +"text entry" { + object { + x=138 + y=494 + width=100 + height=25 + } + control { + chan="$(P)$(M).RRES" + clr=14 + bclr=51 + } + limits { + } +} +text { + object { + x=252 + y=624 + width=73 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="VME Card#" +} +"text update" { + object { + x=331 + y=604 + width=100 + height=18 + } + monitor { + chan="$(P)$(M).VERS" + clr=54 + bclr=3 + } + limits { + } +} +text { + object { + x=252 + y=604 + width=73 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Version" +} +"text entry" { + object { + x=331 + y=644 + width=100 + height=20 + } + control { + chan="$(P)$(M).PREC" + clr=14 + bclr=51 + } + limits { + } +} +text { + object { + x=252 + y=647 + width=73 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Precision" +} +"text update" { + object { + x=331 + y=624 + width=100 + height=18 + } + monitor { + chan="$(P)$(M).CARD" + clr=54 + bclr=3 + } + limits { + } +} +menu { + object { + x=334 + y=368 + width=100 + height=18 + } + control { + chan="$(P)$(M).FOFF" + clr=14 + bclr=51 + } +} +polyline { + object { + x=243 + y=415 + width=3 + height=310 + } + "basic attribute" { + clr=54 + fill="outline" + width=2 + } + points { + (245,416) + (244,724) + } +} +"message button" { + object { + x=111 + y=703 + width=40 + height=20 + } + control { + chan="$(P)allstop.VAL" + clr=30 + bclr=20 + } + label="Abort" + press_msg="1" +} +"message button" { + object { + x=86 + y=703 + width=21 + height=20 + } + control { + chan="$(P)$(M):scanParms.GO" + clr=14 + bclr=51 + } + label="Go" + press_msg="1" +} +"message button" { + object { + x=62 + y=703 + width=21 + height=20 + } + control { + chan="$(P)$(M):scanParms.LOAD" + clr=14 + bclr=51 + } + label="Ld" + press_msg="1" +} +polyline { + object { + x=296 + y=295 + width=143 + height=2 + } + "basic attribute" { + clr=54 + fill="outline" + width=2 + } + points { + (297,296) + (438,296) + } +} +"text entry" { + object { + x=138 + y=619 + width=100 + height=25 + } + control { + chan="$(P)$(M).DLY" + clr=14 + bclr=51 + } + limits { + } +} +"text entry" { + object { + x=88 + y=644 + width=150 + height=25 + } + control { + chan="$(P)$(M).RDBL" + clr=14 + bclr=40 + } + format="string" + limits { + } +} +"related display" { + object { + x=193 + y=703 + width=40 + height=20 + } + display[0] { + label="$(M) (Tiny)" + name="motorx_tiny.adl" + args="P=$(P),M=$(M)" + policy="replace display" + } + display[1] { + label="$(M) (Small)" + name="motorx.adl" + args="P=$(P),M=$(M)" + policy="replace display" + } + display[2] { + label="$(M) (Medium)" + name="motorx_more.adl" + args="P=$(P),M=$(M)" + policy="replace display" + } + display[3] { + label="$(M) (Setup)" + name="motorx_setup.adl" + args="P=$(P),M=$(M)" + policy="replace display" + } + display[4] { + label="Scan Parameters" + name="scanParms.adl" + args="P=$(P),Q=$(M),PV=$(M)" + } + clr=0 + bclr=17 + label="-More" +} +text { + object { + x=406 + y=718 + width=30 + height=10 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="V2.2" +} +composite { + object { + x=252 + y=693 + width=181 + height=20 + } + "composite name"="" + vis="static" + chan="" + children { + "text entry" { + object { + x=283 + y=693 + width=150 + height=20 + } + control { + chan="$(P)$(M).FLNK" + clr=14 + bclr=40 + } + format="string" + limits { + } + } + text { + object { + x=252 + y=693 + width=30 + height=10 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="FWD" + } + text { + object { + x=252 + y=703 + width=30 + height=10 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="LINK" + } + } +} +text { + object { + x=252 + y=669 + width=50 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Torque" +} +"choice button" { + object { + x=305 + y=666 + width=125 + height=20 + } + control { + chan="$(P)$(M).CNEN" + clr=14 + bclr=51 + } + stacking="column" +} +"text update" { + object { + x=174 + y=161 + width=2 + height=2 + } + monitor { + chan="$(P)$(M).LVIO" + clr=30 + bclr=4 + } + align="horiz. centered" + limits { + } +} +text { + object { + x=306 + y=265 + width=130 + height=20 + } + "basic attribute" { + clr=30 + fill="outline" + } + "dynamic attribute" { + vis="if not zero" + chan="$(P)$(M).STAT" + } + textix="Comm. Failure" + align="horiz. centered" +} diff --git a/motorApp/op/adl/motorx_setup.adl b/motorApp/op/adl/motorx_setup.adl new file mode 100644 index 00000000..c37848b1 --- /dev/null +++ b/motorApp/op/adl/motorx_setup.adl @@ -0,0 +1,1373 @@ + +file { + name="/local/epics/3.13.1/synApps_R3.13.1.1/share/stdApp/op/adl/motorx_setup_1.7.adl" + version=020305 +} +display { + object { + x=232 + y=183 + width=400 + height=560 + } + clr=14 + bclr=3 + cmap="" + gridSpacing=5 + gridOn=0 + snapToGrid=0 +} +"color map" { + ncolors=65 + colors { + ffffff, + ececec, + dadada, + c8c8c8, + bbbbbb, + aeaeae, + 9e9e9e, + 919191, + 858585, + 787878, + 696969, + 5a5a5a, + 464646, + 2d2d2d, + 000000, + 00d800, + 1ebb00, + 339900, + 2d7f00, + 216c00, + fd0000, + de1309, + be190b, + a01207, + 820400, + 5893ff, + 597ee1, + 4b6ec7, + 3a5eab, + 27548d, + fbf34a, + f9da3c, + eeb62b, + e19015, + cd6100, + ffb0ff, + d67fe2, + ae4ebc, + 8b1a96, + 610a75, + a4aaff, + 8793e2, + 6a73c1, + 4d52a4, + 343386, + c7bb6d, + b79d5c, + a47e3c, + 7d5627, + 58340f, + 99ffff, + 73dfff, + 4ea5f9, + 2a63e4, + 0a00b8, + ebf1b5, + d4db9d, + bbc187, + a6a462, + 8b8239, + 73ff6b, + 52da3b, + 3cb420, + 289315, + 1a7309, + } +} +rectangle { + object { + x=0 + y=0 + width=400 + height=26 + } + "basic attribute" { + clr=1 + } +} +text { + object { + x=190 + y=90 + width=80 + height=20 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Backlash" + align="horiz. centered" +} +text { + object { + x=110 + y=90 + width=80 + height=20 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Normal" + align="horiz. centered" +} +text { + object { + x=2 + y=90 + width=75 + height=20 + } + "basic attribute" { + clr=0 + fill="outline" + } + textix="Dynamics" +} +text { + object { + x=285 + y=90 + width=110 + height=20 + } + "basic attribute" { + clr=0 + fill="outline" + } + textix="Calibration" +} +polyline { + object { + x=-1 + y=85 + width=401 + height=3 + } + "basic attribute" { + clr=54 + fill="outline" + width=3 + } + points { + (0,86) + (398,86) + } +} +polyline { + object { + x=275 + y=84 + width=3 + height=191 + } + "basic attribute" { + clr=54 + fill="outline" + width=3 + } + points { + (276,85) + (276,273) + } +} +"text entry" { + object { + x=0 + y=0 + width=200 + height=27 + } + control { + chan="$(P)$(M).DESC" + clr=54 + bclr=1 + } + limits { + } +} +text { + object { + x=200 + y=0 + width=200 + height=14 + } + "basic attribute" { + clr=14 + } + textix="($(P)$(M))" + align="horiz. centered" +} +"text update" { + object { + x=200 + y=13 + width=200 + height=14 + } + monitor { + chan="$(P)$(M).DTYP" + clr=54 + bclr=1 + } + align="horiz. centered" + limits { + } +} +polyline { + object { + x=0 + y=27 + width=401 + height=3 + } + "basic attribute" { + clr=54 + fill="outline" + width=3 + } + points { + (1,28) + (399,28) + } +} +text { + object { + x=219 + y=31 + width=100 + height=10 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="DIAL" + align="horiz. centered" +} +text { + object { + x=115 + y=31 + width=100 + height=10 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="USER" + align="horiz. centered" +} +composite { + object { + x=77 + y=39 + width=242 + height=22 + } + "composite name"="" + vis="static" + chan="" + children { + "text entry" { + object { + x=115 + y=39 + width=100 + height=22 + } + control { + chan="$(P)$(M).HLM" + clr=14 + bclr=51 + } + limits { + } + } + "text entry" { + object { + x=219 + y=39 + width=100 + height=22 + } + control { + chan="$(P)$(M).DHLM" + clr=14 + bclr=51 + } + limits { + } + } + text { + object { + x=77 + y=42 + width=35 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="High" + } + } +} +text { + object { + x=2 + y=32 + width=75 + height=20 + } + "basic attribute" { + clr=0 + fill="outline" + } + textix="Limits" +} +"text entry" { + object { + x=115 + y=61 + width=100 + height=22 + } + control { + chan="$(P)$(M).LLM" + clr=14 + bclr=51 + } + limits { + } +} +"text entry" { + object { + x=219 + y=61 + width=100 + height=22 + } + control { + chan="$(P)$(M).DLLM" + clr=14 + bclr=51 + } + limits { + } +} +text { + object { + x=77 + y=64 + width=35 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Low" +} +text { + object { + x=1 + y=117 + width=140 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Max. Speed (Rev/s)" +} +"text entry" { + object { + x=151 + y=114 + width=80 + height=25 + } + control { + chan="$(P)$(M).SMAX" + clr=14 + bclr=51 + } + limits { + } +} +text { + object { + x=3 + y=277 + width=200 + height=20 + } + "basic attribute" { + clr=0 + fill="outline" + } + textix="Resolution, Readback" +} +text { + object { + x=241 + y=325 + width=50 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="/Rev." +} +text { + object { + x=241 + y=400 + width=50 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="/Unit" +} +text { + object { + x=241 + y=375 + width=50 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="/Step" +} +text { + object { + x=241 + y=350 + width=50 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="/Rev." +} +"text update" { + object { + x=171 + y=350 + width=70 + height=18 + } + monitor { + chan="$(P)$(M).EGU" + clr=54 + bclr=3 + } + align="horiz. right" + limits { + } +} +"text update" { + object { + x=171 + y=400 + width=70 + height=18 + } + monitor { + chan="$(P)$(M).EGU" + clr=54 + bclr=3 + } + align="horiz. right" + limits { + } +} +"text update" { + object { + x=171 + y=375 + width=70 + height=18 + } + monitor { + chan="$(P)$(M).EGU" + clr=54 + bclr=3 + } + align="horiz. right" + limits { + } +} +text { + object { + x=171 + y=325 + width=70 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Steps" + align="horiz. right" +} +"text entry" { + object { + x=91 + y=347 + width=80 + height=25 + } + control { + chan="$(P)$(M).UREV" + clr=14 + bclr=51 + } + limits { + } +} +composite { + object { + x=3 + y=372 + width=168 + height=25 + } + "composite name"="" + vis="static" + chan="" + children { + "text entry" { + object { + x=91 + y=372 + width=80 + height=25 + } + control { + chan="$(P)$(M).ERES" + clr=14 + bclr=51 + } + limits { + } + } + text { + object { + x=3 + y=375 + width=60 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Encoder:" + } + } +} +composite { + object { + x=3 + y=397 + width=168 + height=25 + } + "composite name"="" + vis="static" + chan="" + children { + "text entry" { + object { + x=91 + y=397 + width=80 + height=25 + } + control { + chan="$(P)$(M).RRES" + clr=14 + bclr=51 + } + limits { + } + } + text { + object { + x=3 + y=400 + width=70 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Readback:" + } + } +} +"text entry" { + object { + x=91 + y=322 + width=80 + height=25 + } + control { + chan="$(P)$(M).SREV" + clr=14 + bclr=51 + } + limits { + } +} +"text entry" { + object { + x=91 + y=297 + width=80 + height=25 + } + control { + chan="$(P)$(M).EGU" + clr=14 + bclr=51 + } + limits { + } +} +"choice button" { + object { + x=326 + y=375 + width=70 + height=20 + } + control { + chan="$(P)$(M).UEIP" + clr=14 + bclr=51 + } + stacking="column" +} +text { + object { + x=296 + y=375 + width=30 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Use:" +} +"choice button" { + object { + x=326 + y=400 + width=70 + height=20 + } + control { + chan="$(P)$(M).URIP" + clr=14 + bclr=51 + } + stacking="column" +} +text { + object { + x=296 + y=400 + width=30 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Use:" +} +text { + object { + x=3 + y=337 + width=60 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Motor:" +} +text { + object { + x=3 + y=300 + width=60 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Units:" +} +text { + object { + x=216 + y=515 + width=95 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Precision" +} +text { + object { + x=216 + y=497 + width=95 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="VME card#" +} +text { + object { + x=216 + y=477 + width=95 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Code version" +} +text { + object { + x=216 + y=457 + width=75 + height=20 + } + "basic attribute" { + clr=0 + fill="outline" + } + textix="Misc." +} +"text update" { + object { + x=316 + y=477 + width=80 + height=18 + } + monitor { + chan="$(P)$(M).VERS" + clr=54 + bclr=3 + } + limits { + } +} +"text update" { + object { + x=316 + y=497 + width=80 + height=18 + } + monitor { + chan="$(P)$(M).CARD" + clr=54 + bclr=3 + } + limits { + } +} +"text entry" { + object { + x=316 + y=512 + width=80 + height=25 + } + control { + chan="$(P)$(M).PREC" + clr=14 + bclr=51 + } + limits { + } +} +"text entry" { + object { + x=121 + y=502 + width=50 + height=25 + } + control { + chan="$(P)$(M).RTRY" + clr=14 + bclr=51 + } + limits { + } +} +"text entry" { + object { + x=91 + y=477 + width=80 + height=25 + } + control { + chan="$(P)$(M).RDBD" + clr=14 + bclr=51 + } + limits { + } +} +text { + object { + x=3 + y=480 + width=70 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Deadband" +} +text { + object { + x=3 + y=505 + width=120 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Max. retries" +} +text { + object { + x=3 + y=457 + width=127 + height=20 + } + "basic attribute" { + clr=0 + fill="outline" + } + textix="Retry" +} +polyline { + object { + x=191 + y=453 + width=3 + height=94 + } + "basic attribute" { + clr=54 + fill="outline" + width=3 + } + points { + (192,454) + (192,545) + } +} +"related display" { + object { + x=3 + y=525 + width=40 + height=20 + } + display[0] { + label="$(M) (Tiny)" + name="motorx_tiny.adl" + args="P=$(P),M=$(M)" + policy="replace display" + } + display[1] { + label="$(M) (Small)" + name="motorx.adl" + args="P=$(P),M=$(M)" + policy="replace display" + } + display[2] { + label="$(M) (Medium)" + name="motorx_more.adl" + args="P=$(P),M=$(M)" + policy="replace display" + } + display[4] { + label="Scan Parameters" + name="scanParms.adl" + args="P=$(P),Q=$(M),PV=$(M)" + } + display[5] { + label="$(M) (Debug)" + name="motorx_all.adl" + args="P=$(P),M=$(M)" + } + clr=0 + bclr=17 + label="-More" +} +"text entry" { + object { + x=191 + y=242 + width=80 + height=25 + } + control { + chan="$(P)$(M).FRAC" + clr=14 + bclr=51 + } + limits { + } +} +"text entry" { + object { + x=191 + y=217 + width=80 + height=25 + } + control { + chan="$(P)$(M).BDST" + clr=14 + bclr=51 + } + limits { + } +} +"text entry" { + object { + x=191 + y=192 + width=80 + height=25 + } + control { + chan="$(P)$(M).BACC" + clr=14 + bclr=51 + } + limits { + } +} +"text entry" { + object { + x=191 + y=142 + width=80 + height=25 + } + control { + chan="$(P)$(M).SBAK" + clr=14 + bclr=51 + } + limits { + } +} +"text entry" { + object { + x=151 + y=167 + width=80 + height=25 + } + control { + chan="$(P)$(M).SBAS" + clr=14 + bclr=51 + } + limits { + } +} +"text entry" { + object { + x=111 + y=192 + width=80 + height=25 + } + control { + chan="$(P)$(M).ACCL" + clr=14 + bclr=51 + } + limits { + } +} +"text entry" { + object { + x=111 + y=142 + width=80 + height=25 + } + control { + chan="$(P)$(M).S" + clr=14 + bclr=51 + } + limits { + } +} +text { + object { + x=3 + y=195 + width=100 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Accel time (s)" +} +text { + object { + x=3 + y=245 + width=150 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Move fraction" +} +text { + object { + x=3 + y=145 + width=100 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Speed (Rev/s)" +} +text { + object { + x=3 + y=170 + width=125 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Base speed (Rev/s)" +} +text { + object { + x=286 + y=145 + width=25 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Cal" +} +text { + object { + x=286 + y=182 + width=25 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Off" +} +text { + object { + x=286 + y=220 + width=25 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Dir" +} +"choice button" { + object { + x=316 + y=217 + width=80 + height=21 + } + control { + chan="$(P)$(M).DIR" + clr=14 + bclr=51 + } + stacking="column" +} +menu { + object { + x=316 + y=191 + width=80 + height=18 + } + control { + chan="$(P)$(M).FOFF" + clr=14 + bclr=51 + } +} +"text entry" { + object { + x=316 + y=166 + width=80 + height=25 + } + control { + chan="$(P)$(M).OFF" + clr=14 + bclr=51 + } + limits { + } +} +"choice button" { + object { + x=316 + y=143 + width=80 + height=21 + } + control { + chan="$(P)$(M).SET" + clr=14 + bclr=51 + } + stacking="column" +} +text { + object { + x=3 + y=220 + width=190 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Backlash dist.( )" +} +"text update" { + object { + x=121 + y=221 + width=65 + height=14 + } + monitor { + chan="$(P)$(M).EGU" + clr=54 + bclr=3 + } + align="horiz. centered" + limits { + } +} +"text entry" { + object { + x=91 + y=422 + width=200 + height=25 + } + control { + chan="$(P)$(M).RDBL" + clr=14 + bclr=40 + } + format="string" + limits { + } +} +text { + object { + x=3 + y=425 + width=70 + height=18 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="RBV inLink:" +} +"text entry" { + object { + x=301 + y=346 + width=80 + height=25 + } + control { + chan="$(P)$(M).DLY" + clr=14 + bclr=51 + } + limits { + } +} +text { + object { + x=301 + y=317 + width=80 + height=14 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="Readback" + align="horiz. centered" +} +text { + object { + x=301 + y=332 + width=80 + height=14 + } + "basic attribute" { + clr=14 + fill="outline" + } + textix="delay (s)" + align="horiz. centered" +} +polyline { + object { + x=0 + y=452 + width=401 + height=3 + } + "basic attribute" { + clr=54 + fill="outline" + width=3 + } + points { + (1,453) + (399,453) + } +} +polyline { + object { + x=0 + y=272 + width=401 + height=3 + } + "basic attribute" { + clr=54 + fill="outline" + width=3 + } + points { + (1,273) + (399,273) + } +} +text { + object { + x=368 + y=545 + width=25 + height=10 + } + "basic attribute" { + clr=0 + fill="outline" + } + textix="V1.7" +}