Compare commits
101 Commits
Author | SHA1 | Date | |
---|---|---|---|
354e9d90fb | |||
deea821e3f | |||
7a46788fd5 | |||
9e77eb585c | |||
d0c009ea38 | |||
7e1fc78f76 | |||
9e0d8a4322 | |||
3cccfe930c | |||
8860d0c59f | |||
b6c38be113 | |||
b14b50c25a | |||
477ffdbc0b | |||
0a23ec8f22 | |||
eb1bb58c36 | |||
80205727c7 | |||
39098fd0d1 | |||
d44fdbf736 | |||
20e5c35d44 | |||
1539bfc66a | |||
d88e5877a7 | |||
118e177e04 | |||
f61daf0b49 | |||
6dbd0fc0d7 | |||
e657ea675c | |||
5c0c917be9 | |||
35c12274b0 | |||
a6a8f14b26 | |||
33f118ce1e | |||
adf8b30692 | |||
c972cce072 | |||
64c8b08ce4 | |||
30228adf50 | |||
d9d6dae19f | |||
32a8c27dbf | |||
235e403fb3 | |||
b4e201ae86 | |||
9422353107 | |||
e3ac2962f5 | |||
7a81e2c5a0 | |||
f1a17bc295 | |||
8a6441927a | |||
72afd02b4e | |||
f333a27482 | |||
4a2731b054 | |||
9055a86b57 | |||
ccd73babd5 | |||
b8896b7a85 | |||
acf1751081 | |||
61290b5e2c | |||
d706915a46 | |||
3c9932dc18 | |||
9b9072b83b | |||
3b2a21094c | |||
a899a28182 | |||
0cbfe45893 | |||
bb1c10c7cd | |||
339da2e9ff | |||
80877aa6ab | |||
afc92bde3f | |||
8e2c1af10e | |||
f5da0d54bd | |||
ad05433602 | |||
92364a1de8 | |||
403eecafae | |||
fbf2331a05 | |||
14bbda3364 | |||
c7fea08718 | |||
edc71af235 | |||
c805385ad1 | |||
929f9f600d | |||
8c3e68394f | |||
646607e476 | |||
68a265b199 | |||
542abcbaad | |||
efbd0e19cf | |||
6de4a878ef | |||
d86da602d2 | |||
c9e7830274 | |||
d1d74f4db3 | |||
d1beb9b0d8 | |||
3ae13875cf | |||
f56ef2c74c | |||
a25f8cabb9 | |||
c4fe45c0cb | |||
3b7133ecfe | |||
e9a615d0fa | |||
12249d5471 | |||
6d823e2265 | |||
75de7a3a4c | |||
5aefbd4684 | |||
3133d933fa | |||
bf2ff63a4b | |||
b4b093efdf | |||
f424477a6a | |||
3bea34700e | |||
02ab5ff9b8 | |||
972131d86a | |||
f909c41a2b | |||
460030e410 | |||
2a964503a4 | |||
1687ebf4c7 |
236
.clang-format
Normal file
236
.clang-format
Normal file
@ -0,0 +1,236 @@
|
||||
---
|
||||
Language: Cpp
|
||||
# BasedOnStyle: LLVM
|
||||
AccessModifierOffset: -2
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignArrayOfStructures: None
|
||||
AlignConsecutiveAssignments:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
PadOperators: true
|
||||
AlignConsecutiveBitFields:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
PadOperators: false
|
||||
AlignConsecutiveDeclarations:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
PadOperators: false
|
||||
AlignConsecutiveMacros:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
PadOperators: false
|
||||
AlignConsecutiveShortCaseStatements:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCaseColons: false
|
||||
AlignEscapedNewlines: Right
|
||||
AlignOperands: Align
|
||||
AlignTrailingComments:
|
||||
Kind: Always
|
||||
OverEmptyLines: 0
|
||||
AllowAllArgumentsOnNextLine: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: true
|
||||
AllowShortBlocksOnASingleLine: Never
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortEnumsOnASingleLine: true
|
||||
AllowShortFunctionsOnASingleLine: All
|
||||
AllowShortIfStatementsOnASingleLine: Never
|
||||
AllowShortLambdasOnASingleLine: All
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: false
|
||||
AlwaysBreakTemplateDeclarations: MultiLine
|
||||
AttributeMacros:
|
||||
- __capability
|
||||
BinPackArguments: true
|
||||
BinPackParameters: true
|
||||
BitFieldColonSpacing: Both
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: false
|
||||
AfterClass: false
|
||||
AfterControlStatement: Never
|
||||
AfterEnum: false
|
||||
AfterExternBlock: false
|
||||
AfterFunction: false
|
||||
AfterNamespace: false
|
||||
AfterObjCDeclaration: false
|
||||
AfterStruct: false
|
||||
AfterUnion: false
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
BeforeLambdaBody: false
|
||||
BeforeWhile: false
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: true
|
||||
SplitEmptyRecord: true
|
||||
SplitEmptyNamespace: true
|
||||
BreakAfterAttributes: Never
|
||||
BreakAfterJavaFieldAnnotations: false
|
||||
BreakArrays: true
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeConceptDeclarations: Always
|
||||
BreakBeforeBraces: Attach
|
||||
BreakBeforeInlineASMColon: OnlyMultiline
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializers: BeforeColon
|
||||
BreakInheritanceList: BeforeColon
|
||||
BreakStringLiterals: true
|
||||
ColumnLimit: 80
|
||||
CommentPragmas: '^ IWYU pragma:'
|
||||
CompactNamespaces: false
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: true
|
||||
DerivePointerAlignment: false
|
||||
DisableFormat: false
|
||||
EmptyLineAfterAccessModifier: Never
|
||||
EmptyLineBeforeAccessModifier: LogicalBlock
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
FixNamespaceComments: true
|
||||
ForEachMacros:
|
||||
- foreach
|
||||
- Q_FOREACH
|
||||
- BOOST_FOREACH
|
||||
IfMacros:
|
||||
- KJ_IF_MAYBE
|
||||
IncludeBlocks: Preserve
|
||||
IncludeCategories:
|
||||
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
|
||||
Priority: 2
|
||||
SortPriority: 0
|
||||
CaseSensitive: false
|
||||
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
|
||||
Priority: 3
|
||||
SortPriority: 0
|
||||
CaseSensitive: false
|
||||
- Regex: '.*'
|
||||
Priority: 1
|
||||
SortPriority: 0
|
||||
CaseSensitive: false
|
||||
IncludeIsMainRegex: '(Test)?$'
|
||||
IncludeIsMainSourceRegex: ''
|
||||
IndentAccessModifiers: false
|
||||
IndentCaseBlocks: false
|
||||
IndentCaseLabels: false
|
||||
IndentExternBlock: AfterExternBlock
|
||||
IndentGotoLabels: true
|
||||
IndentPPDirectives: None
|
||||
IndentRequiresClause: true
|
||||
IndentWidth: 4
|
||||
IndentWrappedFunctionNames: false
|
||||
InsertBraces: false
|
||||
InsertNewlineAtEOF: false
|
||||
InsertTrailingCommas: None
|
||||
IntegerLiteralSeparator:
|
||||
Binary: 0
|
||||
BinaryMinDigits: 0
|
||||
Decimal: 0
|
||||
DecimalMinDigits: 0
|
||||
Hex: 0
|
||||
HexMinDigits: 0
|
||||
JavaScriptQuotes: Leave
|
||||
JavaScriptWrapImports: true
|
||||
KeepEmptyLinesAtTheStartOfBlocks: true
|
||||
KeepEmptyLinesAtEOF: false
|
||||
LambdaBodyIndentation: Signature
|
||||
LineEnding: DeriveLF
|
||||
MacroBlockBegin: ''
|
||||
MacroBlockEnd: ''
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: None
|
||||
ObjCBinPackProtocolList: Auto
|
||||
ObjCBlockIndentWidth: 2
|
||||
ObjCBreakBeforeNestedBlockParam: true
|
||||
ObjCSpaceAfterProperty: false
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
PackConstructorInitializers: BinPack
|
||||
PenaltyBreakAssignment: 2
|
||||
PenaltyBreakBeforeFirstCallParameter: 19
|
||||
PenaltyBreakComment: 300
|
||||
PenaltyBreakFirstLessLess: 120
|
||||
PenaltyBreakOpenParenthesis: 0
|
||||
PenaltyBreakString: 1000
|
||||
PenaltyBreakTemplateDeclaration: 10
|
||||
PenaltyExcessCharacter: 1000000
|
||||
PenaltyIndentedWhitespace: 0
|
||||
PenaltyReturnTypeOnItsOwnLine: 60
|
||||
PointerAlignment: Right
|
||||
PPIndentWidth: -1
|
||||
QualifierAlignment: Leave
|
||||
ReferenceAlignment: Pointer
|
||||
ReflowComments: true
|
||||
RemoveBracesLLVM: false
|
||||
RemoveParentheses: Leave
|
||||
RemoveSemicolon: false
|
||||
RequiresClausePosition: OwnLine
|
||||
RequiresExpressionIndentation: OuterScope
|
||||
SeparateDefinitionBlocks: Leave
|
||||
ShortNamespaceLines: 1
|
||||
SortIncludes: CaseSensitive
|
||||
SortJavaStaticImport: Before
|
||||
SortUsingDeclarations: LexicographicNumeric
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceAfterTemplateKeyword: true
|
||||
SpaceAroundPointerQualifiers: Default
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCaseColon: false
|
||||
SpaceBeforeCpp11BracedList: false
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeJsonColon: false
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceBeforeParensOptions:
|
||||
AfterControlStatements: true
|
||||
AfterForeachMacros: true
|
||||
AfterFunctionDefinitionName: false
|
||||
AfterFunctionDeclarationName: false
|
||||
AfterIfMacros: true
|
||||
AfterOverloadedOperator: false
|
||||
AfterRequiresInClause: false
|
||||
AfterRequiresInExpression: false
|
||||
BeforeNonEmptyParentheses: false
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceBeforeSquareBrackets: false
|
||||
SpaceInEmptyBlock: false
|
||||
SpacesBeforeTrailingComments: 1
|
||||
SpacesInAngles: Never
|
||||
SpacesInContainerLiterals: true
|
||||
SpacesInLineCommentPrefix:
|
||||
Minimum: 1
|
||||
Maximum: -1
|
||||
SpacesInParens: Never
|
||||
SpacesInParensOptions:
|
||||
InCStyleCasts: false
|
||||
InConditionalStatements: false
|
||||
InEmptyParentheses: false
|
||||
Other: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: Latest
|
||||
StatementAttributeLikeMacros:
|
||||
- Q_EMIT
|
||||
StatementMacros:
|
||||
- Q_UNUSED
|
||||
- QT_REQUIRE_VERSION
|
||||
TabWidth: 4
|
||||
UseTab: Never
|
||||
VerilogBreakBetweenInstancePorts: true
|
||||
WhitespaceSensitiveMacros:
|
||||
- BOOST_PP_STRINGIZE
|
||||
- CF_SWIFT_NAME
|
||||
- NS_SWIFT_NAME
|
||||
- PP_STRINGIZE
|
||||
- STRINGIZE
|
||||
...
|
||||
|
125
.gitignore
vendored
Normal file
125
.gitignore
vendored
Normal file
@ -0,0 +1,125 @@
|
||||
# Took these from the https://github.com/github/gitignore project on October 21, 2011
|
||||
|
||||
# **** 'Personal' entries don't belong in here - put them in your .git/info/exclude file ****
|
||||
|
||||
# Ignore text editor (e.g. emacs) autosave files
|
||||
*~
|
||||
|
||||
# Build Artifacts
|
||||
O.*_Common/
|
||||
O.*_RHEL8-x86_64/
|
||||
|
||||
# Compiled Object files
|
||||
*.slo
|
||||
*.lo
|
||||
*.o
|
||||
*.obj
|
||||
*.d
|
||||
SICServer*
|
||||
|
||||
# Compiled Dynamic libraries
|
||||
*.so
|
||||
|
||||
# Compiled Static libraries
|
||||
*.lai
|
||||
*.la
|
||||
*.a
|
||||
|
||||
# Compiled python files
|
||||
*.py[co]
|
||||
|
||||
# Eclipse-generated files
|
||||
*.pydevproject
|
||||
.project
|
||||
.metadata
|
||||
bin/**
|
||||
tmp/**
|
||||
tmp/**/*
|
||||
*.tmp
|
||||
*.bak
|
||||
*.swp
|
||||
*~.nib
|
||||
local.properties
|
||||
.classpath
|
||||
.settings/
|
||||
.loadpath
|
||||
|
||||
# External tool builders
|
||||
.externalToolBuilders/
|
||||
|
||||
# Locally stored "Eclipse launch configurations"
|
||||
*.launch
|
||||
|
||||
# CDT-specific
|
||||
.cproject
|
||||
|
||||
# PDT-specific
|
||||
.buildpath
|
||||
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
*.sln
|
||||
*.vcproj
|
||||
*.exe
|
||||
*.vcxproj
|
||||
*.filters
|
||||
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
*.sln.docstates
|
||||
*.sdf
|
||||
.cvsignore
|
||||
|
||||
#Test results
|
||||
*.log
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Rr]elease/
|
||||
*_i.c
|
||||
*_p.c
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.vspscc
|
||||
.builds
|
||||
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*
|
||||
|
||||
|
||||
|
||||
# Others
|
||||
*.autosave
|
||||
|
||||
|
||||
# Windows image file caches
|
||||
Thumbs.db
|
||||
|
||||
|
||||
# Mac OS X Finder
|
||||
.DS_Store
|
||||
._*
|
51
.gitlab-ci.yml
Normal file
51
.gitlab-ci.yml
Normal file
@ -0,0 +1,51 @@
|
||||
default:
|
||||
image: docker.psi.ch:5000/wall_e/sinqepics:latest
|
||||
|
||||
stages:
|
||||
- test
|
||||
- build
|
||||
|
||||
cppcheck:
|
||||
stage: test
|
||||
script:
|
||||
- cppcheck --std=c++17 --addon=cert --addon=misc --error-exitcode=1 sinqEPICSApp/
|
||||
allow_failure: true # Long term this needs to be removed
|
||||
artifacts:
|
||||
expire_in: 1 week
|
||||
tags:
|
||||
- docker
|
||||
|
||||
formatting:
|
||||
stage: test
|
||||
script:
|
||||
- clang-format --style=file --Werror --dry-run sinqEPICSApp/src/*.cpp sinqEPICSApp/src/*.c sinqEPICSApp/src/*.h
|
||||
allow_failure: true # Long term this needs to be removed
|
||||
artifacts:
|
||||
expire_in: 1 week
|
||||
tags:
|
||||
- docker
|
||||
|
||||
# clangtidy:
|
||||
# stage: test
|
||||
# script:
|
||||
# - curl https://docker.psi.ch:5000/v2/_catalog
|
||||
# # - dnf update -y
|
||||
# # - dnf install -y clang-tools-extra
|
||||
# # - clang-tidy sinqEPICSApp/src/*.cpp sinqEPICSApp/src/*.c sinqEPICSApp/src/*.h -checks=cppcoreguidelines-*,cert-*
|
||||
# # tags:
|
||||
# # - docker
|
||||
|
||||
build_module:
|
||||
stage: build
|
||||
script:
|
||||
- sed -i 's/ARCH_FILTER=.*/ARCH_FILTER=linux%/' Makefile.RHEL8
|
||||
- make -f Makefile.RHEL8 install
|
||||
- cp -rT "/ioc/modules/sinq/$(ls -U /ioc/modules/sinq/ | head -1)" "./sinq-${CI_COMMIT_SHORT_SHA}"
|
||||
artifacts:
|
||||
name: "sinq-${CI_COMMIT_SHORT_SHA}"
|
||||
paths:
|
||||
- "sinq-${CI_COMMIT_SHORT_SHA}/*"
|
||||
expire_in: 1 week
|
||||
when: always
|
||||
tags:
|
||||
- docker
|
1
10-llbDevices.rules
Normal file
1
10-llbDevices.rules
Normal file
@ -0,0 +1 @@
|
||||
SUBSYSTEM=="usb", ATTR{idVendor}=="04b4", ATTR{idProduct}=="1002", MODE="0666" GROUP="plugdev", TAG+="uaccess"
|
22
MakeAutoSave.RHEL7
Normal file
22
MakeAutoSave.RHEL7
Normal file
@ -0,0 +1,22 @@
|
||||
# This Makefile has to be executed in the synApps-xxx/support/autosave-xxx directory.
|
||||
# It creates a autosave module to load into iocsh
|
||||
include /ioc/tools/driver.makefile
|
||||
|
||||
MODULE=autosave
|
||||
|
||||
BUILDCLASSES=Linux
|
||||
|
||||
USR_DEPENDENCIES=
|
||||
|
||||
SOURCES += asApp/src/dbrestore.c
|
||||
SOURCES += asApp/src/save_restore.c
|
||||
SOURCES += asApp/src/initHooks.c
|
||||
SOURCES += asApp/src/fGetDateStr.c
|
||||
SOURCES += asApp/src/configMenuSub.c
|
||||
SOURCES += asApp/src/verify.c
|
||||
SOURCES += asApp/src/asVerify.c
|
||||
SOURCES += asApp/src/os/Linux/osdNfs.c
|
||||
|
||||
HEADERS += asApp/src/os/Linux/osdNfs.h
|
||||
|
||||
DBDS += asApp/src/asSupport.dbd
|
17
MakeScaler.RHEL7
Normal file
17
MakeScaler.RHEL7
Normal file
@ -0,0 +1,17 @@
|
||||
include /ioc/tools/driver.makefile
|
||||
|
||||
MODULE=scaler
|
||||
|
||||
BUILDCLASSES=Linux
|
||||
|
||||
USR_DEPENDENCIES = asyn,427.0.1
|
||||
|
||||
SOURCES += stdApp/src/scalerRecord.c
|
||||
SOURCES += stdApp/src/devScalerAsyn.c
|
||||
SOURCES += stdApp/src/drvScalerSoft.c
|
||||
|
||||
HEADERS += devScaler.h
|
||||
|
||||
|
||||
DBDS += stdApp/src/scalerRecord.dbd
|
||||
DBDS += stdApp/src/scalerSupport.dbd
|
@ -2,12 +2,13 @@
|
||||
include /ioc/tools/driver.makefile
|
||||
|
||||
MODULE=sinq
|
||||
|
||||
BUILDCLASSES=Linux
|
||||
EPICS_VERSIONS=3.14.12 7.0.4.1
|
||||
|
||||
# additional module dependencies
|
||||
REQUIRED+=SynApps
|
||||
REQUIRED+=stream
|
||||
REQUIRED+=scaler
|
||||
|
||||
# using a test version
|
||||
scaler_VERSION=koennecke
|
||||
@ -26,8 +27,10 @@ SOURCES += sinqEPICSApp/src/EL734Driver.cpp
|
||||
SOURCES += sinqEPICSApp/src/NanotecDriver.cpp
|
||||
SOURCES += sinqEPICSApp/src/stptok.cpp
|
||||
SOURCES += sinqEPICSApp/src/PhytronDriver.cpp
|
||||
SOURCES += sinqEPICSApp/src/EuroMoveDriver.cpp
|
||||
SOURCES += sinqEPICSApp/src/pmacAsynIPPort.c
|
||||
SOURCES += sinqEPICSApp/src/pmacAxis.cpp
|
||||
SOURCES += sinqEPICSApp/src/pmacController.cpp
|
||||
|
||||
SOURCES += sinqEPICSApp/src/drvAsynMasterMACSPort.c
|
||||
SOURCES += sinqEPICSApp/src/MasterMACSDriver.cpp
|
||||
# MISCS would be the place to keep the stream device template files
|
||||
|
37
Makefile.RHEL7.michele
Normal file
37
Makefile.RHEL7.michele
Normal file
@ -0,0 +1,37 @@
|
||||
# This build the sinq extensions for the PSI EPICS setup
|
||||
include /ioc/tools/driver.makefile
|
||||
|
||||
MODULE=sinq
|
||||
LIBVERSION=brambilla_m_test
|
||||
BUILDCLASSES=Linux
|
||||
EPICS_VERSIONS=3.14.12 7.0.4.1
|
||||
ARCH_FILTER=RHEL7-x86_64
|
||||
|
||||
# additional module dependencies
|
||||
REQUIRED+=SynApps
|
||||
REQUIRED+=stream
|
||||
REQUIRED+=scaler
|
||||
|
||||
# using a test version
|
||||
scaler_VERSION=koennecke
|
||||
|
||||
TEMPLATES += sinqEPICSApp/Db/dimetix.db
|
||||
TEMPLATES += sinqEPICSApp/Db/slsvme.db
|
||||
TEMPLATES += sinqEPICSApp/Db/spsamor.db
|
||||
|
||||
DBDS += sinqEPICSApp/src/sinq.dbd
|
||||
|
||||
# What we need at SINQ
|
||||
SOURCES += sinqEPICSApp/src/devScalerEL737.c
|
||||
SOURCES += sinqEPICSApp/src/SINQController.cpp
|
||||
SOURCES += sinqEPICSApp/src/SINQAxis.cpp
|
||||
SOURCES += sinqEPICSApp/src/EL734Driver.cpp
|
||||
SOURCES += sinqEPICSApp/src/NanotecDriver.cpp
|
||||
SOURCES += sinqEPICSApp/src/stptok.cpp
|
||||
SOURCES += sinqEPICSApp/src/PhytronDriver.cpp
|
||||
SOURCES += sinqEPICSApp/src/EuroMoveDriver.cpp
|
||||
SOURCES += sinqEPICSApp/src/pmacAsynIPPort.c
|
||||
SOURCES += sinqEPICSApp/src/pmacAxis.cpp
|
||||
SOURCES += sinqEPICSApp/src/pmacController.cpp
|
||||
|
||||
# MISCS would be the place to keep the stream device template files
|
42
Makefile.RHEL8
Normal file
42
Makefile.RHEL8
Normal file
@ -0,0 +1,42 @@
|
||||
# This build the sinq extensions for the PSI EPICS setup
|
||||
include /ioc/tools/driver.makefile
|
||||
|
||||
MODULE=sinq
|
||||
BUILDCLASSES=Linux
|
||||
EPICS_VERSIONS=7.0.7
|
||||
ARCH_FILTER=RHEL8%
|
||||
|
||||
# additional module dependencies
|
||||
REQUIRED+=SynApps
|
||||
REQUIRED+=stream
|
||||
REQUIRED+=scaler
|
||||
REQUIRED+=motorBase
|
||||
|
||||
# DB files to include in the release
|
||||
TEMPLATES += sinqEPICSApp/Db/dimetix.db
|
||||
TEMPLATES += sinqEPICSApp/Db/slsvme.db
|
||||
TEMPLATES += sinqEPICSApp/Db/spsamor.db
|
||||
TEMPLATES += sinqEPICSApp/Db/el734.db
|
||||
|
||||
# DBD files to include in the release
|
||||
DBDS += sinqEPICSApp/src/sinq.dbd
|
||||
|
||||
# Source files to build
|
||||
SOURCES += sinqEPICSApp/src/devScalerEL737.c
|
||||
SOURCES += sinqEPICSApp/src/SINQController.cpp
|
||||
SOURCES += sinqEPICSApp/src/SINQAxis.cpp
|
||||
SOURCES += sinqEPICSApp/src/EL734Driver.cpp
|
||||
SOURCES += sinqEPICSApp/src/NanotecDriver.cpp
|
||||
SOURCES += sinqEPICSApp/src/stptok.cpp
|
||||
SOURCES += sinqEPICSApp/src/PhytronDriver.cpp
|
||||
SOURCES += sinqEPICSApp/src/EuroMoveDriver.cpp
|
||||
# SOURCES += sinqEPICSApp/src/pmacAsynIPPort.c
|
||||
# SOURCES += sinqEPICSApp/src/pmacAxis.cpp
|
||||
# SOURCES += sinqEPICSApp/src/pmacController.cpp
|
||||
SOURCES += sinqEPICSApp/src/MasterMACSDriver.cpp
|
||||
SOURCES += sinqEPICSApp/src/C804Axis.cpp
|
||||
SOURCES += sinqEPICSApp/src/C804Controller.cpp
|
||||
|
||||
USR_CFLAGS += -Wall -Wextra # -Werror
|
||||
|
||||
# MISCS would be the place to keep the stream device template files
|
49
README.md
Normal file
49
README.md
Normal file
@ -0,0 +1,49 @@
|
||||
# SinqEPICSApp
|
||||
|
||||
This is an IOC application which contains the drivers for SINQ hardware.
|
||||
EPICS purists may find it messy because it contains the drivers for all
|
||||
SINQ stuff in one package rather then in separate modules. This is just
|
||||
for ease of distribution. The idea being that all of SINQ shares one
|
||||
module/IOC application and configures in its startup file what it needs
|
||||
of it.
|
||||
|
||||
Drivers included:
|
||||
|
||||
- EL734 motor controller
|
||||
- SINQ Delta Tau PMAC motor controller. This is a modified version of the Diamond
|
||||
PMAC code. The modification is necessary because PSI runs a different program in
|
||||
the MCU at SINQ then Diamond
|
||||
- Nanotec bus motor driver
|
||||
- Phytron MCU200 motor driver
|
||||
- EL737 counter box driver through the scaler record
|
||||
|
||||
There are some streamdevice protocol files provided:
|
||||
- spss5: for the Siemens SPS5 with the PSI custom RS232 module
|
||||
- slsvme: for the SLS magnet controllers managed by the SINQ special vmemaggi program
|
||||
- el755: the old SINQ magnet controller
|
||||
- dimetix: for the deimetix distance measurement device at AMOR
|
||||
|
||||
There are two main branches here:
|
||||
|
||||
- Of course master from which you can build the PSI RHEL7 module and the EEE module
|
||||
- The amorsim branch in which you can build the IOC application for ESS against the ICS
|
||||
setup. This is needed for the sinq-amorsim simulation. This branch exists because of the
|
||||
political problem to get a module into the ICS EEE setup.
|
||||
|
||||
Those political problems require a special development model:
|
||||
|
||||
1. If you do something, do it in a separate branch
|
||||
2. When reasonably sure that what you did works, merge it both into master and amorsim
|
||||
|
||||
Take care of the sinqEPICsApp/src/sinq.dbd file. This is the one which differs mostly between
|
||||
amorsim and master branches.
|
||||
|
||||
# Formatting
|
||||
|
||||
Formatting is done via the [`.clang-format`](./.clang-format) file checked into
|
||||
the repository. One option to apply the formatting to a given file is via the
|
||||
command below.
|
||||
|
||||
```
|
||||
clang-format -i -style=file <file>
|
||||
```
|
@ -25,17 +25,20 @@ TEMPLATE_TOP=$(EPICS_BASE)/templates/makeBaseApp/top
|
||||
#SNCSEQ=$(EPICS_BASE)/../modules/soft/seq
|
||||
|
||||
# EPICS_BASE usually appears last so other apps can override stuff:
|
||||
EPICS_BASE=/opt/epics/bases/base-3.14.12.5
|
||||
|
||||
#EPICS_BASE=
|
||||
#MODULES=/home/epics/modules
|
||||
|
||||
# Set RULES here if you want to take build rules from somewhere
|
||||
# other than EPICS_BASE:
|
||||
#RULES=/path/to/epics/support/module/rules/x-y
|
||||
MOTOR=/opt/epics/modules/motor/6.10.0/3.14.12.5
|
||||
ASYN=/opt/epics/modules/asyn/4.27.0/3.14.12.5
|
||||
SYNAPPSSTD=/opt/epics/modules/synAppsStd/3.4.1/3.14.12.5/
|
||||
#ANC=/usr/local/epics/anc350v17
|
||||
STREAMS=/opt/epics/modules/streamdevice/2.6.0/3.14.12.5
|
||||
#LAKESHORE336=/usr/local/epics/support/lakeshore336
|
||||
BUSY=/opt/epics/modules/busy/1.6.0/3.14.12.5
|
||||
#OXINSTCRYOJET=/usr/local/epics/support/OxInstCryojet-2-18-3
|
||||
PCRE=/opt/epics/modules/pcre/8.36.0/3.14.12.5
|
||||
##RULES=/path/to/epics/support/module/rules/x-y
|
||||
#MOTOR=/opt/epics/modules/motor/6.10.0/3.14.12.5
|
||||
#ASYN=/opt/epics/modules/asyn/4.27.0/3.14.12.5
|
||||
#SYNAPPSSTD=/opt/epics/modules/synAppsStd/3.4.1/3.14.12.5/
|
||||
##ANC=/usr/local/epics/anc350v17
|
||||
#STREAMS=/opt/epics/modules/streamdevice/2.6.0/3.14.12.5
|
||||
##LAKESHORE336=/usr/local/epics/support/lakeshore336
|
||||
#BUSY=/opt/epics/modules/busy/1.6.0/3.14.12.5
|
||||
##OXINSTCRYOJET=/usr/local/epics/support/OxInstCryojet-2-18-3
|
||||
#PCRE=/opt/epics/modules/pcre/8.36.0/3.14.12.5
|
||||
|
||||
|
23
iocBoot/iocsinqEPICS/euromovetest.cmd
Executable file
23
iocBoot/iocsinqEPICS/euromovetest.cmd
Executable file
@ -0,0 +1,23 @@
|
||||
#!/usr/local/bin/iocsh
|
||||
|
||||
require sinq,koennecke
|
||||
|
||||
epicsEnvSet("TOP","/afs/psi.ch/project/sinqdev/sinqepicsapp/iocBoot/iocsinqEPICS")
|
||||
epicsEnvSet("BASE","/afs/psi.ch/project/sinqdev/sinqepicsapp")
|
||||
|
||||
cd ${TOP}
|
||||
|
||||
|
||||
|
||||
drvAsynIPPortConfigure("serial1", "129.129.195.83:1234",0,0,0)
|
||||
#drvAsynIPPortConfigure("serial1", "amor-ts:3002",0,0,0)
|
||||
#drvAsynIPPortConfigure("serial1", "localhost:9090",0,0,0)
|
||||
EuroMoveCreateController("llb","serial1", 10, 1);
|
||||
|
||||
### Motors
|
||||
|
||||
dbLoadRecords("$(BASE)/sinqEPICSApp/Db/asynRecord.db","P=KM36:,R=serial1,PORT=serial1,ADDR=0,OMAX=80,IMAX=80")
|
||||
dbLoadTemplate "motor.substitutions.eurollb"
|
||||
|
||||
|
||||
iocInit
|
@ -1,4 +1,4 @@
|
||||
file "$(MOTOR)/db/basic_asyn_motor.db"
|
||||
qfile "$(MOTOR)/db/basic_asyn_motor.db"
|
||||
{
|
||||
pattern
|
||||
{P, N, M, DTYP, PORT, ADDR, DESC, EGU, DIR, VELO, VBAS, ACCL, BDST, BVEL, BACC, MRES, PREC, DHLM, DLLM, INIT}
|
||||
|
13
iocBoot/iocsinqEPICS/motor.substitutions.eurollb
Normal file
13
iocBoot/iocsinqEPICS/motor.substitutions.eurollb
Normal file
@ -0,0 +1,13 @@
|
||||
file "$(BASE)/sinqEPICSApp/Db/basic_asyn_motor.db"
|
||||
{
|
||||
pattern
|
||||
{P, N, M, DTYP, PORT, ADDR, DESC, EGU, DIR, VELO, VBAS, ACCL, BDST, BVEL, BACC, MRES, PREC, RDBD, DHLM, DLLM, INIT}
|
||||
{KM36:llb:, 1, "m$(N)", "asynMotor", llb, 1, "m1", mm, Pos, 2.0, 0.1, .2, 0, 1, .2, .01, 3, 0.2, 1000, 0, "1"}
|
||||
}
|
||||
|
||||
file "$(BASE)/sinqEPICSApp/Db/motorMessage.db"
|
||||
{
|
||||
pattern
|
||||
{P,N, M,PORT}
|
||||
{KM36:llb:, 1, "m$(N)",llb}
|
||||
}
|
@ -1,7 +1,15 @@
|
||||
file "$(MOTOR)/db/basic_asyn_motor.db"
|
||||
file "$(BASE)/sinqEPICSApp/Db/basic_asyn_motor.db"
|
||||
{
|
||||
pattern
|
||||
{P, N, M, DTYP, PORT, ADDR, DESC, EGU, DIR, VELO, VBAS, ACCL, BDST, BVEL, BACC, MRES, PREC, DHLM, DLLM, INIT}
|
||||
{KM36:phytron:, 1, "m$(N)", "asynMotor", phy, 1, "m1", mm, Pos, 2.0, 0.1, .2, 0, 1, .2, .001, 3, 100, -100, "1"}
|
||||
{KM36:phytron:, 2, "m$(N)", "asynMotor", phy, 2, "m2", mm, Pos, 2.0, 0.1, .2, 0, 1, .2, .001, 3, 100, -100, "10"}
|
||||
{P, N, M, DTYP, PORT, ADDR, DESC, EGU, DIR, VELO, VBAS, ACCL, BDST, BVEL, BACC, MRES, PREC, RDBD, DHLM, DLLM, INIT}
|
||||
{KM36:phytron:, 1, "m$(N)", "asynMotor", phy, 1, "m1", mm, Pos, 2.0, 0.1, .2, 0, 1, .2, .001, 3, 0.2, 100, -100, "1"}
|
||||
{KM36:phytron:, 2, "m$(N)", "asynMotor", phy, 2, "m2", mm, Pos, 2.0, 0.1, .2, 0, 1, .2, .001, 3, 0.2, 100, -100, "10"}
|
||||
}
|
||||
|
||||
file "$(BASE)/sinqEPICSApp/Db/motorMessage.db"
|
||||
{
|
||||
pattern
|
||||
{P,N, M,PORT}
|
||||
{KM36:phytron:, 1, "m$(N)",phy}
|
||||
{KM36:phytron:, 2, "m$(N)",phy}
|
||||
}
|
25
iocBoot/iocsinqEPICS/phytest.cmd
Executable file
25
iocBoot/iocsinqEPICS/phytest.cmd
Executable file
@ -0,0 +1,25 @@
|
||||
#!/usr/local/bin/iocsh
|
||||
|
||||
require sinq,koennecke
|
||||
|
||||
epicsEnvSet("TOP","/afs/psi.ch/project/sinqdev/sinqepicsapp/iocBoot/iocsinqEPICS")
|
||||
epicsEnvSet("BASE","/afs/psi.ch/project/sinqdev/sinqepicsapp")
|
||||
epicsEnvSet("dbPATH","${EPICS_BASE}/dbd:${ASYN}/dbd:${MOTOR}/dbd")
|
||||
|
||||
cd ${TOP}
|
||||
|
||||
## Register all support components
|
||||
dbLoadDatabase "../../dbd/sinqEPICS.dbd"
|
||||
|
||||
|
||||
drvAsynIPPortConfigure("serial1", "129.129.195.58:22222",0,0,0)
|
||||
#drvAsynIPPortConfigure("serial1", "localhost:9090",0,0,0)
|
||||
PhytronCreateController("phy","serial1","0",1,0);
|
||||
|
||||
### Motors
|
||||
|
||||
dbLoadRecords("$(BASE)/sinqEPICSApp/Db/asynRecord.db","P=KM36:,R=serial1,PORT=serial1,ADDR=0,OMAX=80,IMAX=80")
|
||||
dbLoadTemplate "motor.substitutions.phytron"
|
||||
|
||||
|
||||
iocInit
|
@ -18,19 +18,16 @@ epicsEnvSet("dbPATH","${EPICS_BASE}/dbd::${ASYN}/dbd:${MOTOR}/dbd:${ANC}/dbd")
|
||||
cd ${TOP}
|
||||
|
||||
## Register all support components
|
||||
dbLoadDatabase "../../dbd/sinqEPICS.dbd"
|
||||
#dbLoadDatabase "dbd/sinq.dbd"
|
||||
dbLoadDatabase "../../sinqEPICSApp/src/sinq.dbd"
|
||||
sinqEPICS_registerRecordDeviceDriver pdbbase
|
||||
|
||||
## Load record instances
|
||||
#dbLoadRecords("db/xxx.db","user=koenneckeHost")
|
||||
|
||||
|
||||
#---------- load Nanotec motor controller
|
||||
#drvAsynIPPortConfigure("serial1", "narziss-ts:3002",0,0,0)
|
||||
#drvAsynIPPortConfigure("serial1", "localhost:5050",0,0,0)
|
||||
drvAsynIPPortConfigure("serial1", "localhost:8080",0,0,0)
|
||||
PhytronCreateController("phy","serial1",1,1);
|
||||
PhytronCreateController("phy","serial1","0",1,1);
|
||||
|
||||
### Motors
|
||||
|
||||
|
94
iocBoot/iocsinqEPICS/slsvme.db
Normal file
94
iocBoot/iocsinqEPICS/slsvme.db
Normal file
@ -0,0 +1,94 @@
|
||||
# Database definition for the SLS VME magnets as installed at AMOR
|
||||
|
||||
##
|
||||
## Read the High Limit
|
||||
##
|
||||
record(ai, "$(PREFIX)HighLim") {
|
||||
field(DTYP, "stream")
|
||||
field(DESC, "High Current Limit")
|
||||
field(INP, "@slsvme.proto read($(NO),hl) slsvme 0")
|
||||
field(SCAN, "1 second")
|
||||
field(PREC, "3")
|
||||
field(EGU, "A")
|
||||
}
|
||||
|
||||
##
|
||||
## Read the High Limit
|
||||
##
|
||||
record(ai, "$(PREFIX)LowLim") {
|
||||
field(DTYP, "stream")
|
||||
field(DESC, "Low Current Limit")
|
||||
field(INP, "@slsvme.proto read($(NO),ll) slsvme 0")
|
||||
field(SCAN, "1 second")
|
||||
field(PREC, "3")
|
||||
field(EGU, "A")
|
||||
}
|
||||
|
||||
##
|
||||
## Read the Error code
|
||||
##
|
||||
record(ai, "$(PREFIX)ErrCode") {
|
||||
field(DTYP, "stream")
|
||||
field(DESC, "Error Code")
|
||||
field(INP, "@slsvme.proto read($(NO),err) slsvme 0")
|
||||
field(SCAN, "1 second")
|
||||
field(PREC, "3")
|
||||
field(EGU, "A")
|
||||
}
|
||||
|
||||
##
|
||||
## Read the textual representation of the error.
|
||||
##
|
||||
record(stringin, "$(PREFIX)ErrText") {
|
||||
field(DTYP, "stream")
|
||||
field(INP, "@slsvme.proto readErrTxt($(NO)) slsvme 0")
|
||||
field(SCAN, "1 second")
|
||||
field(PINI, "YES")
|
||||
}
|
||||
|
||||
|
||||
##
|
||||
## Read the Current
|
||||
##
|
||||
record(ai, "$(PREFIX)CurRBV") {
|
||||
field(DTYP, "stream")
|
||||
field(DESC, "Low Current Limit")
|
||||
field(INP, "@slsvme.proto read($(NO),cur) slsvme 0")
|
||||
field(SCAN, "1 second")
|
||||
field(PREC, "3")
|
||||
field(EGU, "A")
|
||||
}
|
||||
|
||||
##
|
||||
## Set the current
|
||||
##
|
||||
record(ao, "$(PREFIX)CurSet") {
|
||||
field(DTYP, "stream")
|
||||
field(DESC, "Setpoint current")
|
||||
field(OUT, "@slsvme.proto write($(NO),cur) slsvme 0")
|
||||
field(PREC, "3")
|
||||
field(EGU, "A")
|
||||
}
|
||||
|
||||
##
|
||||
## Read power status of the magnet
|
||||
##
|
||||
record(bi, "$(PREFIX)PowerStatusRBV") {
|
||||
field(DESC, "Readback of the power status")
|
||||
field(DTYP, "stream")
|
||||
field(INP, "@slsvme.proto readonoff($(NO)) slsvme 0")
|
||||
field(SCAN, "1 second")
|
||||
field(ZNAM, "off")
|
||||
field(ONAM, "on")
|
||||
}
|
||||
|
||||
##
|
||||
## Set the power status
|
||||
##
|
||||
record(bo, "$(PREFIX)PowerStatus") {
|
||||
field(DESC, "Set the power status")
|
||||
field(DTYP, "stream")
|
||||
field(OUT, "@slsvme.proto setpower($(NO)) slsvme 0")
|
||||
field(ZNAM, "on")
|
||||
field(ONAM, "off")
|
||||
}
|
@ -4,4 +4,4 @@ set dir [pwd]
|
||||
|
||||
cd /usr/local/ioc
|
||||
|
||||
exec /usr/bin/tar czvf $dir/sinqepics.tgz modules/scaler modules/sinq modules/anc350
|
||||
exec /usr/bin/tar czvf $dir/sinqepics.tgz modules/scaler modules/sinq modules/anc350 modules/autosave
|
||||
|
9
sinqEPICSApp/Db/asynRecord.db
Normal file
9
sinqEPICSApp/Db/asynRecord.db
Normal file
@ -0,0 +1,9 @@
|
||||
record(asyn,"$(P)$(R)")
|
||||
{
|
||||
field(DTYP,"asynRecordDevice")
|
||||
field(PORT,"$(PORT)")
|
||||
field(ADDR,"$(ADDR)")
|
||||
field(OMAX,"$(OMAX)")
|
||||
field(IMAX,"$(IMAX)")
|
||||
}
|
||||
|
24
sinqEPICSApp/Db/basic_asyn_motor.db
Normal file
24
sinqEPICSApp/Db/basic_asyn_motor.db
Normal file
@ -0,0 +1,24 @@
|
||||
record(motor,"$(P)$(M)")
|
||||
{
|
||||
field(DESC,"$(DESC)")
|
||||
field(DTYP,"$(DTYP)")
|
||||
field(DIR,"$(DIR)")
|
||||
field(VELO,"$(VELO)")
|
||||
field(VMAX,"$(VMAX=0)")
|
||||
field(VBAS,"$(VBAS)")
|
||||
field(ACCL,"$(ACCL)")
|
||||
field(BDST,"$(BDST)")
|
||||
field(BVEL,"$(BVEL)")
|
||||
field(BACC,"$(BACC)")
|
||||
field(OUT,"@asyn($(PORT),$(ADDR))")
|
||||
field(MRES,"$(MRES)")
|
||||
field(PREC,"$(PREC)")
|
||||
field(EGU,"$(EGU)")
|
||||
field(DHLM,"$(DHLM)")
|
||||
field(DLLM,"$(DLLM)")
|
||||
field(INIT,"$(INIT)")
|
||||
field(TWV,"1")
|
||||
field(RDBD,"$(RDBD)")
|
||||
field(RTRY,"0")
|
||||
}
|
||||
|
21
sinqEPICSApp/Db/basic_motor.db
Normal file
21
sinqEPICSApp/Db/basic_motor.db
Normal file
@ -0,0 +1,21 @@
|
||||
grecord(motor,"$(P)$(M)")
|
||||
{
|
||||
field(DESC,"$(DESC)")
|
||||
field(DTYP,"$(DTYP)")
|
||||
field(DIR,"$(DIR)")
|
||||
field(VELO,"$(VELO)")
|
||||
field(VBAS,"$(VBAS)")
|
||||
field(ACCL,"$(ACCL)")
|
||||
field(BDST,"$(BDST)")
|
||||
field(BVEL,"$(BVEL)")
|
||||
field(BACC,"$(BACC)")
|
||||
field(OUT,"#C$(C) S$(S) @")
|
||||
field(MRES,"$(MRES)")
|
||||
field(PREC,"$(PREC)")
|
||||
field(EGU,"$(EGU)")
|
||||
field(DHLM,"$(DHLM)")
|
||||
field(DLLM,"$(DLLM)")
|
||||
field(INIT,"$(INIT)")
|
||||
field(TWV,"1")
|
||||
}
|
||||
|
34
sinqEPICSApp/Db/el734.db
Normal file
34
sinqEPICSApp/Db/el734.db
Normal file
@ -0,0 +1,34 @@
|
||||
record(motor,"$(P)$(M)")
|
||||
{
|
||||
field(DESC,"$(DESC)")
|
||||
field(DTYP,"$(DTYP)")
|
||||
field(DIR,"$(DIR)")
|
||||
field(VELO,"$(VELO)")
|
||||
field(HVEL,"$(VELO)")
|
||||
field(VBAS,"$(VELO)")
|
||||
field(VMAX, "${VMAX}")
|
||||
field(ACCL,"$(ACCL)")
|
||||
field(BDST,"$(BDST)")
|
||||
field(BVEL,"$(BVEL)")
|
||||
field(BACC,"$(BACC)")
|
||||
field(OUT,"@asyn($(PORT),$(ADDR))")
|
||||
field(MRES,"$(MRES)")
|
||||
field(PREC,"$(PREC)")
|
||||
field(EGU,"$(EGU)")
|
||||
field(DHLM,"$(DHLM)")
|
||||
field(DLLM,"$(DLLM)")
|
||||
field(INIT,"$(INIT)")
|
||||
field(PINI, "NO")
|
||||
field(TWV,"1")
|
||||
field(RTRY,"0")
|
||||
}
|
||||
|
||||
# The message text
|
||||
record(waveform, "$(P)$(M)-MsgTxt") {
|
||||
field(DTYP, "asynOctetRead")
|
||||
field(INP, "@asyn($(PORT),$(N),1) MOTOR_MESSAGE_TEXT")
|
||||
field(FTVL, "CHAR")
|
||||
field(NELM, "80")
|
||||
field(SCAN, "I/O Intr")
|
||||
}
|
||||
|
@ -6,4 +6,13 @@ record(waveform, "$(P)$(M)-MsgTxt") {
|
||||
field(FTVL, "CHAR")
|
||||
field(NELM, "80")
|
||||
field(SCAN, "I/O Intr")
|
||||
}
|
||||
}
|
||||
|
||||
record(ao,"$(P)m$(N)-Resolution"){
|
||||
field(DESC,"m$(N) Resolution")
|
||||
field(DOL,"$(P)m$(N).MRES CP MS")
|
||||
field(OMSL,"closed_loop")
|
||||
field(DTYP,"asynFloat64")
|
||||
field(OUT,"@asyn($(PORT),$(N))MOTOR_RESOLUTION")
|
||||
field(PREC,"3")
|
||||
}
|
||||
|
13
sinqEPICSApp/Db/motorSet.db
Normal file
13
sinqEPICSApp/Db/motorSet.db
Normal file
@ -0,0 +1,13 @@
|
||||
# workaround over set position
|
||||
record(ao, "$(P)$(M)-SetPosition") {
|
||||
field(DTYP, "asynFloat64")
|
||||
field(OUT, "@asyn($(PORT),$(N),1) SET_MOTOR_POSITION")
|
||||
field(PINI, "YES")
|
||||
}
|
||||
|
||||
# enable axis
|
||||
record(longout, "$(P)$(M):Enable") {
|
||||
field(DTYP, "asynInt32")
|
||||
field(OUT, "@asyn($(PORT),$(N),1) ENABLE_AXIS")
|
||||
field(PINI, "YES")
|
||||
}
|
54
sinqEPICSApp/Db/sinq_asyn_motor.db
Normal file
54
sinqEPICSApp/Db/sinq_asyn_motor.db
Normal file
@ -0,0 +1,54 @@
|
||||
record(motor,"$(P)$(M)")
|
||||
{
|
||||
field(DESC,"$(DESC)")
|
||||
field(DTYP,"$(DTYP)")
|
||||
field(DIR,"$(DIR)")
|
||||
field(VELO,"$(VELO)")
|
||||
field(VMAX,"$(VMAX=$(VELO))")
|
||||
field(VBAS,"$(VBAS)")
|
||||
field(ACCL,"$(ACCL)")
|
||||
field(BDST,"$(BDST)")
|
||||
field(BVEL,"$(BVEL)")
|
||||
field(BACC,"$(BACC)")
|
||||
field(OUT,"@asyn($(PORT),$(ADDR))")
|
||||
field(MRES,"$(MRES)")
|
||||
field(PREC,"$(PREC)")
|
||||
field(EGU,"$(EGU)")
|
||||
field(DHLM,"$(DHLM)")
|
||||
field(DLLM,"$(DLLM)")
|
||||
field(INIT,"$(INIT)")
|
||||
field(TWV,"1")
|
||||
field(RDBD,"$(RDBD)")
|
||||
}
|
||||
|
||||
|
||||
# The message text
|
||||
record(waveform, "$(P)$(M)-MsgTxt") {
|
||||
field(DTYP, "asynOctetRead")
|
||||
field(INP, "@asyn($(PORT),$(N),1) MOTOR_MESSAGE_TEXT")
|
||||
field(FTVL, "CHAR")
|
||||
field(NELM, "80")
|
||||
field(SCAN, "I/O Intr")
|
||||
}
|
||||
|
||||
# enable axis
|
||||
record(longout, "$(P)$(M):Enable") {
|
||||
field(DTYP, "asynInt32")
|
||||
field(OUT, "@asyn($(PORT),$(N),1) ENABLE_AXIS")
|
||||
field(PINI, "YES")
|
||||
}
|
||||
|
||||
# enable axis
|
||||
record(longin, "$(P)$(M):Enable_RBV") {
|
||||
field(DTYP, "asynInt32")
|
||||
field(INP, "@asyn($(PORT),$(N),1) AXIS_ENABLED")
|
||||
field(PINI, "YES")
|
||||
field(SCAN, "1 second")
|
||||
}
|
||||
|
||||
# reread encoder
|
||||
record(longout, "$(P)$(M):Reread_Encoder") {
|
||||
field(DTYP, "asynInt32")
|
||||
field(OUT, "@asyn($(PORT),$(N),1) REREAD_ENCODER_POSITION")
|
||||
field(PINI, "NO")
|
||||
}
|
477
sinqEPICSApp/src/C804Axis.cpp
Normal file
477
sinqEPICSApp/src/C804Axis.cpp
Normal file
@ -0,0 +1,477 @@
|
||||
#include "C804Axis.h"
|
||||
#include "C804Controller.h"
|
||||
#include <cmath>
|
||||
#include <errlog.h>
|
||||
#include <limits>
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
C804Axis::C804Axis(C804Controller *pC, int axisNo)
|
||||
: SINQAxis(pC, axisNo), pC_(pC) {
|
||||
/*
|
||||
The superclass constructor SINQAxis calls in turn its superclass constructor
|
||||
asynMotorAxis. In the latter, a pointer to the constructed object this is
|
||||
stored inside the array pAxes_:
|
||||
|
||||
pC->pAxes_[axisNo] = this;
|
||||
|
||||
Therefore, the axes are managed by the controller pC. See C804Controller.cpp
|
||||
for further explanation. If axisNo is out of bounds, asynMotorAxis prints an
|
||||
error (see
|
||||
https://github.com/epics-modules/motor/blob/master/motorApp/MotorSrc/asynMotorAxis.cpp,
|
||||
line 40). However, we want the IOC creation to stop completely, since this
|
||||
is a configuration error.
|
||||
*/
|
||||
if (axisNo >= pC->numAxes_) {
|
||||
exit(-1);
|
||||
}
|
||||
last_position_steps_ = 0;
|
||||
|
||||
last_poll_ = 0.0;
|
||||
}
|
||||
|
||||
C804Axis::~C804Axis(void) {
|
||||
// Since the controller memory is managed somewhere else, we don't need to
|
||||
// clean up the pointer pC here.
|
||||
}
|
||||
|
||||
/*
|
||||
The polling function informs us about the state of the axis, in particular if it
|
||||
is currently moving. It is called periodically, with the period defined by
|
||||
the controller constructor arguments idlePollPeriod and movingPollPeriod
|
||||
depending on the current axis state.
|
||||
*/
|
||||
asynStatus C804Axis::poll(bool *moving) {
|
||||
// Local variable declaration
|
||||
static const char *functionName = "C804Axis::poll";
|
||||
|
||||
// The poll function is just a wrapper around poll_no_param_lib_update and
|
||||
// handles mainly the callParamCallbacks() function
|
||||
asynStatus status_poll = C804Axis::poll_no_param_lib_update(moving);
|
||||
|
||||
// According to the function documentation of asynMotorAxis::poll, this
|
||||
// function should be called at the end of a poll implementation.
|
||||
asynStatus status_callback = callParamCallbacks();
|
||||
if (status_callback != asynSuccess) {
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"%s: Updating the parameter library failed for axis %d\n",
|
||||
functionName, axisNo_);
|
||||
return status_callback;
|
||||
} else {
|
||||
return status_poll;
|
||||
}
|
||||
}
|
||||
|
||||
// Perform the actual poll
|
||||
asynStatus C804Axis::poll_no_param_lib_update(bool *moving) {
|
||||
// Local variable declaration
|
||||
static const char *functionName = "C804Axis::poll";
|
||||
asynStatus status;
|
||||
int axis_status = 0;
|
||||
|
||||
// The controller returns the position and velocity in encoder steps.
|
||||
// This value needs to be converted in user units (engineering units EGU)
|
||||
// via the record field MRES of the motor record. This field has already
|
||||
// been read by the constructor into the member variable
|
||||
// motorRecResolution_. To go from steps to user units, multiply with
|
||||
// motorRecResolution_ Example: If 10 steps correspond to 1 mm, MRES should
|
||||
// be 0.1.
|
||||
int position_error_steps = 0;
|
||||
int motor_position_steps = 0;
|
||||
int motor_velocity_steps = 0;
|
||||
int programmed_motor_velocity_steps = 0;
|
||||
double position_error = .0;
|
||||
double motor_position = .0;
|
||||
double motor_velocity = .0;
|
||||
double programmed_motor_velocity = .0;
|
||||
|
||||
// The buffer sizes for command and response are defined in the controller
|
||||
// (see the corresponding source code files)
|
||||
char command[pC_->C804_MAXBUF_], response[pC_->C804_MAXBUF_];
|
||||
|
||||
/*
|
||||
Cancel the poll if the last poll has "just" happened.
|
||||
*/
|
||||
if (time(NULL) < last_poll_ + 0.5 * pC_->movingPollPeriod_) {
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_WARNING,
|
||||
"%s: Aborted poll since the last poll for axis %d happened a "
|
||||
"short time ago\n",
|
||||
functionName, axisNo_);
|
||||
return asynSuccess;
|
||||
} else {
|
||||
last_poll_ = time(NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
The parameter motorRecResolution_ is coupled to the field MRES of the motor
|
||||
record in the following manner:
|
||||
- In sinq_asyn_motor.db, the PV (motor_record_pv_name)MOTOR_REC_RESOLUTION
|
||||
is defined as a copy of the field (motor_record_pv_name).MRES:
|
||||
|
||||
record(ao,"$(P)$(M):Resolution") {
|
||||
field(DESC, "$(M) resolution")
|
||||
field(DOL, "$(P)$(M).MRES CP MS")
|
||||
field(OMSL, "closed_loop")
|
||||
field(DTYP, "asynFloat64")
|
||||
field(OUT, "@asyn($(PORT),$(ADDR))MOTOR_REC_RESOLUTION")
|
||||
field(PREC, "$(PREC)")
|
||||
}
|
||||
|
||||
- The PV name MOTOR_REC_RESOLUTION is coupled in asynMotorController.h to
|
||||
the constant motorRecResolutionString
|
||||
- ... which in turn is assigned to motorRecResolution_ in
|
||||
asynMotorController.cpp This way of making the field visible to the driver
|
||||
is described here: https://epics.anl.gov/tech-talk/2020/msg00378.php This is
|
||||
a one-way coupling, changes to the parameter library via setDoubleParam are
|
||||
NOT transferred to (motor_record_pv_name).MRES or to
|
||||
(motor_record_pv_name):Resolution.
|
||||
|
||||
NOTE: This function must not be called in the constructor (e.g. in order to
|
||||
save the read result to the member variable earlier), since the parameter
|
||||
library is updated at a later stage!
|
||||
*/
|
||||
pC_->getDoubleParam(axisNo_, pC_->motorRecResolution_,
|
||||
&motorRecResolution_);
|
||||
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW, "Poll axis %d\n", axisNo_);
|
||||
|
||||
/*
|
||||
We know that the motor resolution must not be zero. During the startup of
|
||||
the IOC, polls can happen before the record is fully initialized. In that
|
||||
case, all values are zero.
|
||||
*/
|
||||
if (motorRecResolution_ == 0) {
|
||||
return asynError;
|
||||
}
|
||||
|
||||
/*
|
||||
Assume that the axis does not have a status problem. If it does have a
|
||||
problem, this value will be overwritten further below. Setting this value
|
||||
in itself does not trigger a callback immediately, any callbacks
|
||||
(such as e.g. updating camonitor) are done in callParamCallbacks() at the
|
||||
end of this function.
|
||||
*/
|
||||
setIntegerParam(pC_->motorStatusProblem_, false);
|
||||
|
||||
// Read out the position error of the axis (delta of target position to
|
||||
// actual position)
|
||||
snprintf(command, pC_->C804_MAXBUF_ - 1, "%dTE", axisNo_);
|
||||
status = pC_->lowLevelWriteRead(axisNo_, command, response, true);
|
||||
if (status == asynSuccess) {
|
||||
int parsed_axis;
|
||||
sscanf(response, "%2dE%10d", &parsed_axis, &position_error_steps);
|
||||
|
||||
// Scale from the encoder resultion to user units
|
||||
position_error = double(position_error_steps) * motorRecResolution_;
|
||||
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW,
|
||||
"%s: Axis %d, response %s, position error %f\n", functionName,
|
||||
axisNo_, response, position_error);
|
||||
} else {
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"%s: Reading the position error failed for axis %d\n",
|
||||
functionName, axisNo_);
|
||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
|
||||
// Stop the evaluation prematurely
|
||||
return status;
|
||||
}
|
||||
|
||||
// Read the current position.
|
||||
snprintf(command, this->pC_->C804_MAXBUF_ - 1, "%dTP", this->axisNo_);
|
||||
status =
|
||||
this->pC_->lowLevelWriteRead(this->axisNo_, command, response, true);
|
||||
if (status == asynSuccess) {
|
||||
int parsed_axis;
|
||||
sscanf(response, "%2dP%10d", &parsed_axis, &motor_position_steps);
|
||||
|
||||
// Scale from the encoder resultion to user units
|
||||
motor_position = double(motor_position_steps) * motorRecResolution_;
|
||||
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW,
|
||||
"%s: Axis %d, response %s, position %f\n", functionName,
|
||||
axisNo_, response, motor_position);
|
||||
setDoubleParam(pC_->motorPosition_, motor_position);
|
||||
setDoubleParam(pC_->motorEncoderPosition_, motor_position);
|
||||
} else {
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"%s: Reading the position failed for axis %d\n", functionName,
|
||||
axisNo_);
|
||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
// Read the current velocity
|
||||
snprintf(command, this->pC_->C804_MAXBUF_ - 1, "%dTV", this->axisNo_);
|
||||
status =
|
||||
this->pC_->lowLevelWriteRead(this->axisNo_, command, response, true);
|
||||
if (status == asynSuccess) {
|
||||
int parsed_axis;
|
||||
sscanf(response, "%2dV%10d", &parsed_axis, &motor_velocity_steps);
|
||||
|
||||
// Scale from the encoder resultion to user units
|
||||
motor_velocity = double(motor_velocity_steps) * motorRecResolution_;
|
||||
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW,
|
||||
"%s: Axis %d, response %s, velocity %f\n", functionName,
|
||||
axisNo_, response, motor_velocity);
|
||||
} else {
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"%s: Reading the velocity failed for axis %d\n", functionName,
|
||||
axisNo_);
|
||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
// Read the programmed velocity
|
||||
snprintf(command, this->pC_->C804_MAXBUF_ - 1, "%dTY", this->axisNo_);
|
||||
status =
|
||||
this->pC_->lowLevelWriteRead(this->axisNo_, command, response, true);
|
||||
if (status == asynSuccess) {
|
||||
|
||||
int parsed_axis;
|
||||
sscanf(response, "%2dY%10d", &parsed_axis,
|
||||
&programmed_motor_velocity_steps);
|
||||
|
||||
// Scale from the encoder resultion to user units
|
||||
programmed_motor_velocity =
|
||||
double(programmed_motor_velocity_steps) * motorRecResolution_;
|
||||
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW,
|
||||
"%s: Axis %d, response %s, programmed velocity %f\n",
|
||||
functionName, axisNo_, response, programmed_motor_velocity);
|
||||
} else {
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"%s: Reading the programmed velocity failed for axis %d\n",
|
||||
functionName, axisNo_);
|
||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
// Read the motor status
|
||||
snprintf(command, pC_->C804_MAXBUF_ - 1, "%dTS", axisNo_);
|
||||
status = pC_->lowLevelWriteRead(this->axisNo_, command, response, true);
|
||||
if (status == asynSuccess) {
|
||||
int parsed_axis;
|
||||
sscanf(response, "%2dS%10d", &parsed_axis, &axis_status);
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW,
|
||||
"%s: Axis %d, response %s, status %d\n", functionName,
|
||||
axisNo_, response, axis_status);
|
||||
} else {
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"%s: Reading the motor status %d\n", functionName, axisNo_);
|
||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
|
||||
// Stop prematurely
|
||||
return status;
|
||||
}
|
||||
|
||||
// Check if the axis is enabled by reading out bit 2 (see
|
||||
// https://stackoverflow.com/questions/2249731/how-do-i-get-bit-by-bit-data-from-an-integer-value-in-c)
|
||||
int mask = 1 << 2;
|
||||
int masked_n = axis_status & mask;
|
||||
// Is 1 if the axis is disabled
|
||||
int disabled = masked_n >> 2;
|
||||
if (disabled) {
|
||||
enabled_ = false;
|
||||
} else {
|
||||
enabled_ = true;
|
||||
}
|
||||
|
||||
/*
|
||||
Determine if the motor is moving. This is determined by the following
|
||||
criteria: 1) The motor position changes from poll to poll 2) The motor is
|
||||
enabled
|
||||
*/
|
||||
*moving = enabled_ && motor_position_steps != this->last_position_steps_;
|
||||
|
||||
// Update the cached_position
|
||||
this->last_position_steps_ = motor_position_steps;
|
||||
|
||||
/*
|
||||
Calculate the time the motor should need to reach its target, based on the
|
||||
programmed velocity and compare this to the actual time the motor has spent
|
||||
moving. If it has spent too much time in a moving state without reaching
|
||||
the target, stop the motor and return an error.
|
||||
*/
|
||||
if (*moving) {
|
||||
|
||||
int motorStatusMoving = 0;
|
||||
pC_->getIntegerParam(axisNo_, pC_->motorStatusMoving_,
|
||||
&motorStatusMoving);
|
||||
|
||||
// motor is moving, but didn't move in the last poll
|
||||
if (motorStatusMoving == 0) {
|
||||
time_t current_time = time(NULL);
|
||||
|
||||
// Factor 2 of the calculated moving time
|
||||
estimatedArrivalTime_ =
|
||||
current_time + std::ceil(2 * std::fabs(position_error) /
|
||||
programmed_motor_velocity);
|
||||
} else {
|
||||
// /*
|
||||
// Motor is moving for a longer time than it should: Stop it
|
||||
// */
|
||||
// if (time(NULL) > estimatedArrivalTime_)
|
||||
// {
|
||||
// snprintf(command, pC_->C804_MAXBUF_ - 1, "%dST", axisNo_);
|
||||
// status = pC_->lowLevelWriteRead(axisNo_, command, response);
|
||||
// asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR, "%s: Stopped
|
||||
// axis %d since it moved for double the time it should to reach
|
||||
// its target\n", functionName, axisNo_);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
One of these parameters (or both) are used to set (PV-name).DMOV.
|
||||
This PV tells EPICS whether the axis / motor is currently moving or not.
|
||||
*/
|
||||
setIntegerParam(pC_->motorStatusMoving_, *moving);
|
||||
setIntegerParam(pC_->motorStatusDone_, !(*moving));
|
||||
callParamCallbacks();
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
asynStatus C804Axis::move(double position, int relative, double minVelocity,
|
||||
double maxVelocity, double acceleration) {
|
||||
asynStatus status;
|
||||
static const char *functionName = "C804Axis::move";
|
||||
char command[pC_->C804_MAXBUF_], response[pC_->C804_MAXBUF_];
|
||||
double position_c_units = 0.0; // Controller units
|
||||
int position_steps = 0;
|
||||
|
||||
// Convert from user coordinates (EGU) to controller coordinates (steps).
|
||||
// Check for overflow
|
||||
if (motorRecResolution_ == 0.0) {
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"%s: MRES must not be zero. Movement is aborted",
|
||||
functionName);
|
||||
return asynError;
|
||||
}
|
||||
position_c_units = position / motorRecResolution_;
|
||||
|
||||
// Check for overflow during the division
|
||||
if (position_c_units * motorRecResolution_ != position) {
|
||||
asynPrint(
|
||||
pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"%s: could not convert from user units (%f) to controller units "
|
||||
"(user units divided by resolution MRES %f) due to overflow.",
|
||||
functionName, position, motorRecResolution_);
|
||||
return asynError;
|
||||
}
|
||||
|
||||
// Steps can only be integer values => cast to integer while checking for
|
||||
// overflow
|
||||
if (std::numeric_limits<int>::max() < position_c_units ||
|
||||
std::numeric_limits<int>::min() > position_c_units) {
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"%s: target position %f cannot be converted to int "
|
||||
"(overflow). Check target value %f and MRES %f",
|
||||
functionName, position_c_units, position_c_units,
|
||||
motorRecResolution_);
|
||||
return asynError;
|
||||
}
|
||||
position_steps = static_cast<int>(position_c_units);
|
||||
|
||||
// Convert from relative to absolute values
|
||||
if (relative) {
|
||||
position_steps += last_position_steps_;
|
||||
}
|
||||
|
||||
// If the axis is currently disabled, enable it
|
||||
if (!enabled_) {
|
||||
snprintf(command, pC_->C804_MAXBUF_ - 1, "%dGO", axisNo_);
|
||||
status =
|
||||
pC_->lowLevelWriteRead(this->axisNo_, command, response, false);
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"%s: Enabling axis %d\n failed", functionName, axisNo_);
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
// Start movement
|
||||
snprintf(command, pC_->C804_MAXBUF_ - 1, "%dMA%d", axisNo_, position_steps);
|
||||
status = pC_->lowLevelWriteRead(this->axisNo_, command, response, false);
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"%s: Setting the target position %d failed for axis %d\n",
|
||||
functionName, position_steps, axisNo_);
|
||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
return status;
|
||||
}
|
||||
setIntegerParam(pC_->motorStatusProblem_, false);
|
||||
|
||||
// Reset the error flag
|
||||
errorReported_ = 0;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
asynStatus C804Axis::moveVelocity(double min_velocity, double max_velocity,
|
||||
double acceleration) {
|
||||
static const char *functionName = "C804Axis::moveVelocity";
|
||||
return asynError;
|
||||
}
|
||||
|
||||
asynStatus C804Axis::stop(double acceleration) {
|
||||
asynStatus status = asynSuccess;
|
||||
static const char *functionName = "C804Axis::stop";
|
||||
char command[pC_->C804_MAXBUF_], response[pC_->C804_MAXBUF_];
|
||||
|
||||
bool moving = false;
|
||||
|
||||
poll(&moving);
|
||||
if (moving) {
|
||||
// ST = Stop
|
||||
snprintf(command, pC_->C804_MAXBUF_ - 1, "%dST", axisNo_);
|
||||
status = pC_->lowLevelWriteRead(axisNo_, command, response, false);
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACEIO_DEVICE, "%s: Stop axis %d\n",
|
||||
functionName, axisNo_);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
asynStatus C804Axis::home(double minVelocity, double maxVelocity,
|
||||
double acceleration, int forwards) {
|
||||
asynStatus status = asynSuccess;
|
||||
static const char *functionName = "C804Axis::home";
|
||||
char command[pC_->C804_MAXBUF_], response[pC_->C804_MAXBUF_];
|
||||
|
||||
snprintf(command, pC_->C804_MAXBUF_ - 1, "%dFE0",
|
||||
axisNo_); // Home to the upper limit of the axis (25 mm)
|
||||
status = pC_->lowLevelWriteRead(axisNo_, command, response, false);
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACEIO_DEVICE, "%s: Homing axis %d\n",
|
||||
functionName, axisNo_);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
If on is 0, disable the motor, otherwise enable it.
|
||||
*/
|
||||
asynStatus C804Axis::enable(int on) {
|
||||
asynStatus status = asynSuccess;
|
||||
static const char *functionName = "C804Axis::enable";
|
||||
char command[pC_->C804_MAXBUF_], response[pC_->C804_MAXBUF_];
|
||||
|
||||
if (on == 0) {
|
||||
snprintf(command, pC_->C804_MAXBUF_ - 1, "%dMF", axisNo_);
|
||||
status = pC_->lowLevelWriteRead(axisNo_, command, response, false);
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACEIO_DEVICE,
|
||||
"%s: Disable axis %d\n", functionName, axisNo_);
|
||||
} else {
|
||||
snprintf(command, pC_->C804_MAXBUF_ - 1, "%dMN", axisNo_);
|
||||
status = pC_->lowLevelWriteRead(axisNo_, command, response, false);
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACEIO_DEVICE,
|
||||
"%s: Enable axis %d\n", functionName, axisNo_);
|
||||
}
|
||||
return status;
|
||||
}
|
40
sinqEPICSApp/src/C804Axis.h
Normal file
40
sinqEPICSApp/src/C804Axis.h
Normal file
@ -0,0 +1,40 @@
|
||||
#ifndef C804Axis_H
|
||||
#define C804Axis_H
|
||||
|
||||
#include "SINQController.h"
|
||||
#include "SINQAxis.h"
|
||||
|
||||
// Forward declaration of the controller class to resolve the cyclic dependency
|
||||
// between C804Controller.h and C804Axis.h. See https://en.cppreference.com/w/cpp/language/class.
|
||||
class C804Controller;
|
||||
|
||||
class C804Axis : public SINQAxis
|
||||
{
|
||||
public:
|
||||
/* These are the methods we override from the base class */
|
||||
C804Axis(C804Controller *pController, int axisNo);
|
||||
virtual ~C804Axis();
|
||||
asynStatus move(double position, int relative, double min_velocity, double max_velocity, double acceleration);
|
||||
asynStatus moveVelocity(double min_velocity, double max_velocity, double acceleration);
|
||||
asynStatus stop(double acceleration);
|
||||
asynStatus home(double minVelocity, double maxVelocity, double acceleration, int forwards);
|
||||
asynStatus poll(bool *moving);
|
||||
asynStatus poll_no_param_lib_update(bool *moving);
|
||||
asynStatus enable(int on);
|
||||
|
||||
protected:
|
||||
C804Controller *pC_;
|
||||
|
||||
void checkBounds(C804Controller *pController, int axisNo);
|
||||
int last_position_steps_;
|
||||
double motorRecResolution_;
|
||||
time_t estimatedArrivalTime_;
|
||||
time_t last_poll_;
|
||||
int errorReported_;
|
||||
bool enabled_;
|
||||
|
||||
private:
|
||||
friend class C804Controller;
|
||||
};
|
||||
|
||||
#endif
|
458
sinqEPICSApp/src/C804Controller.cpp
Normal file
458
sinqEPICSApp/src/C804Controller.cpp
Normal file
@ -0,0 +1,458 @@
|
||||
/**
|
||||
Overview EPICS documentation
|
||||
- https://docs.epics-controls.org/en/latest/index.html
|
||||
- https://epics.anl.gov/modules/soft/asyn/R4-29/asynDriver.html
|
||||
- https://www.physicstom.com/epics/
|
||||
- https://epics.anl.gov/modules/soft/asyn/R4-20/asynDriver.pdf
|
||||
*/
|
||||
|
||||
#include "C804Controller.h"
|
||||
#include "asynOctetSyncIO.h"
|
||||
#include <errlog.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <iocsh.h>
|
||||
#include <epicsExport.h>
|
||||
#include <registryFunction.h>
|
||||
|
||||
const double C804Controller::C804_TIMEOUT_ = 5.0;
|
||||
|
||||
static const char *driverName = "C804Controller";
|
||||
/*
|
||||
In SINQ, this constructor is usually called by its C interface function via the IOC shell.
|
||||
A typical call looks like that (taken from SANS instrument st.cmd):
|
||||
|
||||
pmacAsynIPConfigure("pmcu1","sans1-mcu1:1025")
|
||||
pmacV3CreateController("mcu1","pmcu1",0,9,50,10000);
|
||||
pmacV3CreateAxis("mcu1",1,0);
|
||||
pmacV3CreateAxis("mcu1",2,0);
|
||||
pmacV3CreateAxis("mcu1",3,0);
|
||||
pmacV3CreateAxis("mcu1",4,0);
|
||||
pmacV3CreateAxis("mcu1",5,0);
|
||||
pmacV3CreateAxis("mcu1",6,0);
|
||||
pmacV3CreateAxis("mcu1",7,0);
|
||||
pmacV3CreateAxis("mcu1",8,0);
|
||||
|
||||
The first call creates a port object in EPICS (port 1025 in this specific case) with the name "pmcu1".
|
||||
|
||||
The second call creates the controller with the following arguments:
|
||||
- portName = "mcu1": The controller is registered by this name in EPICS. The axes
|
||||
constructors below use the name to get the controller pointer from EPICS.
|
||||
- lowLevelPortName = "pmcu1": The EPICS controller object connects to the physical
|
||||
device via the port object "pmcu1".
|
||||
- lowLevelPortAddress = 0: Connects to port 0 of asynOctetSyncIO. asynOctetSyncIO
|
||||
is an interface for ASCII-string-based message communication between driver and device.
|
||||
This value seems to be always zero (at least for all pmacAsynIPConfigure in the SINQ IOCs)
|
||||
- numAxes = 9: Constructs the array "pAxes_" with space for 9 axis pointers (see below).
|
||||
In the axes constructors below, we use the array indices 1 to 8, hence leaving the
|
||||
first element of the array unitialized (it has index 0). This is mainly a convenience
|
||||
to avoid talking about an "axis 0" when meaning the first axis.
|
||||
- movingPollPeriod: The method C804Controller::poll is called in a loop every
|
||||
movingPollPeriod seconds when the axis is moving
|
||||
- idlePollPeriod: The method C804Controller::poll is called in a loop every
|
||||
idlePollPeriod seconds when the axis is not moving
|
||||
*/
|
||||
C804Controller::C804Controller(const char *portName, const char *lowLevelPortName, int lowLevelPortAddress,
|
||||
int numAxes, double movingPollPeriod, double idlePollPeriod, const int &extraParams)
|
||||
: SINQController(portName, lowLevelPortName, numAxes, extraParams)
|
||||
{
|
||||
|
||||
// Definition of local variables.
|
||||
asynStatus status = asynSuccess;
|
||||
|
||||
/*
|
||||
functionName is overwritten by the name of the current function in each method
|
||||
of C804Controller. This is used for feedback messages to the user.
|
||||
*/
|
||||
static const char *functionName = "C804Controller::C804Controller";
|
||||
|
||||
/*
|
||||
Update: We don't need this array, since all axes accesses are done
|
||||
with the getAxis-function, which accesses the base class array
|
||||
|
||||
============================================================================
|
||||
|
||||
The array pAxes_ is a member of the superclass asynMotorController and is
|
||||
allocated when calling the constructor of asynMotorController, which
|
||||
is done by the constructor of SINQController:
|
||||
|
||||
pAxes_ = (asynMotorAxis**) calloc(numAxes, sizeof(asynMotorAxis*));
|
||||
|
||||
Therefore, on this line we create a pointer to that array while interpreting
|
||||
every pointer in it as one to a C804Axis. This is valid because we populate
|
||||
the array in the constructor of C804Axis and can therefore be sure that
|
||||
all asynMotorAxis pointers in asynMotorController::pAxes_ are in fact pointers
|
||||
to C804Axis axes.
|
||||
|
||||
Interestingly, this array usually is leaked after destruction of asynMotorController
|
||||
(n this class, we clean it up in the constructor).
|
||||
The reason for that is that the EPICS devices are usually created only once at
|
||||
the start of the program and the memory is cleaned up by the OS.
|
||||
*/
|
||||
// pAxes_ = (C804Axis **)(asynMotorController::pAxes_);
|
||||
|
||||
// Initialize non static data members
|
||||
lowLevelPortUser_ = NULL;
|
||||
movingPollPeriod_ = movingPollPeriod;
|
||||
idlePollPeriod_ = idlePollPeriod;
|
||||
|
||||
/*
|
||||
We try to connect to the port via the port name provided by the constructor.
|
||||
If this fails, we return an error message.
|
||||
*/
|
||||
status = pasynOctetSyncIO->connect(lowLevelPortName, lowLevelPortAddress, &lowLevelPortUser_, NULL);
|
||||
if (status != asynSuccess)
|
||||
{
|
||||
/*
|
||||
ASYN_TRACE_ERROR is a mask for the trace of asynUser (member variable this->lowLevelPortUser_)
|
||||
The different mask options are listed on this page: https://epics.anl.gov/modules/soft/asyn/R4-29/asynDriver.html (section AsynTrace):
|
||||
|
||||
0x1 ASYN_TRACE_ERROR Run time errors are reported, e.g. timeouts.
|
||||
0x2 ASYN_TRACEIO_DEVICE Device support reports I/O activity.
|
||||
0x4 ASYN_TRACEIO_FILTER Any layer between device support and the low level driver reports any filtering it does on I/O.
|
||||
0x8 ASYN_TRACEIO_DRIVER Low level driver reports I/O activity.
|
||||
0x10 ASYN_TRACE_FLOW Report logic flow. Device support should report all queue requests, callbacks entered, and all calls to drivers. Layers between device support and low level drivers should report all calls they make to lower level drivers. Low level drivers report calls they make to other support.
|
||||
0x20 ASYN_TRACE_WARNING Report warnings, i.e. conditions that are between ASYN_TRACE_ERROR and ASYN_TRACE_FLOW.
|
||||
|
||||
To see the output of these functions e.g. on the shell, a trace mask needs
|
||||
to be set:
|
||||
|
||||
asynSetTraceIOMask("L0", -1, 0x2)
|
||||
asynSetTraceMask("L0", -1, 0x9) <- this enables 0x8 + 0x1 => ASYN_TRACE_ERROR and ASYN_TRACEIO_DRIVER
|
||||
|
||||
https://github-wiki-see.page/m/ISISComputingGroup/ibex_developers_manual/wiki/ASYN-Trace-Masks-(Debugging-IOC,-ASYN)
|
||||
|
||||
Here, we are unable to connect to the controller, which is a runtime error -> ASYN_TRACE_ERROR.
|
||||
However, since lowLevelPortUser_ might still be NULL (because
|
||||
pasynOctetSyncIO->connect failed for some reason), we use the alternative
|
||||
EPICS function errlogPrintf. This function does not provide a timestamp
|
||||
and masking facilities.
|
||||
*/
|
||||
// asynPrint(this->lowLevelPortUser_, ASYN_TRACE_ERROR,
|
||||
// "%s: cannot connect to C804 controller\n",
|
||||
// functionName); // Asyn-framework function, needs to be configured by setTraceMasks
|
||||
errlogPrintf("Fatal error in %s: cannot connect to C804 controller\n", functionName);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
/*
|
||||
Here we define the terminators for messages sent to / received from the
|
||||
physical device (Eos = End of string).
|
||||
In the C804 manual, the terminator for an outgoing message is specified as
|
||||
(rtn) == Carriage Return. From https://en.wikipedia.org/wiki/Escape_sequences_in_C:
|
||||
(rtn) => \r
|
||||
|
||||
An incoming report is terminated by a CRLF ETX (again referring to https://en.wikipedia.org/wiki/Escape_sequences_in_C)
|
||||
CR => \r
|
||||
LF (line feed) = \n
|
||||
ETX (end-of-text, see https://www.asciitable.com/) => \x03
|
||||
*/
|
||||
const char *message_to_device = "\r";
|
||||
const char *message_from_device = "\x03";
|
||||
pasynOctetSyncIO->setOutputEos(lowLevelPortUser_, message_to_device, strlen(message_to_device)); // Output: from EPICS to device
|
||||
pasynOctetSyncIO->setInputEos(lowLevelPortUser_, message_from_device, strlen(message_from_device)); // Input: from device to EPICS
|
||||
|
||||
/*
|
||||
See documentation of function in asynMotorController.cpp:
|
||||
"Starts the motor poller thread.
|
||||
* Derived classes will typically call this at near the end of their constructor.
|
||||
[...]
|
||||
"
|
||||
The function arguments are:
|
||||
* movingPollPeriod The time between polls when any axis is moving (in seconds).
|
||||
* idlePollPeriod The time between polls when no axis is moving (in seconds).
|
||||
* forcedFastPolls The number of times to force the movingPollPeriod after waking up the poller.
|
||||
*/
|
||||
startPoller(movingPollPeriod, idlePollPeriod, 1);
|
||||
|
||||
/*
|
||||
After changing values in the parameter library (e.g. by calls to setIntegerParam),
|
||||
the PV's need to be updated. This is done explictly by callParamCallbacks()
|
||||
callParamCallbacks due to the separation asyn - EPICS (param lib vs. driver support)
|
||||
*/
|
||||
callParamCallbacks();
|
||||
}
|
||||
|
||||
C804Controller::~C804Controller(void)
|
||||
{
|
||||
/*
|
||||
Cleanup of the memory allocated in this->pAxes_. As discussed in the constructor,
|
||||
this is not strictly necessary due to the way EPICS works, but it is good
|
||||
practice anyway to properly clean up resources.
|
||||
*/
|
||||
free(this->pAxes_);
|
||||
}
|
||||
|
||||
/*
|
||||
Access one of the axes of the controller via the axis adress stored in asynUser.
|
||||
If the axis does not exist or is not a C804Axis, a nullptr is returned and an error is emitted.
|
||||
*/
|
||||
C804Axis *C804Controller::getAxis(asynUser *pasynUser)
|
||||
{
|
||||
asynMotorAxis *asynAxis = asynMotorController::getAxis(pasynUser);
|
||||
return C804Controller::castToC804Axis(asynAxis);
|
||||
}
|
||||
|
||||
/*
|
||||
Access one of the axes of the controller via the axis index.
|
||||
If the axis does not exist or is not a C804Axis, the function must return Null
|
||||
*/
|
||||
C804Axis *C804Controller::getAxis(int axisNo)
|
||||
{
|
||||
asynMotorAxis *asynAxis = asynMotorController::getAxis(axisNo);
|
||||
return C804Controller::castToC804Axis(asynAxis);
|
||||
}
|
||||
|
||||
C804Axis *C804Controller::castToC804Axis(asynMotorAxis *asynAxis)
|
||||
{
|
||||
static const char *functionName = "C804Controller::getAxis";
|
||||
|
||||
// If the axis slot of the pAxes_ array is empty, a nullptr must be returned
|
||||
if (asynAxis == nullptr)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Here, an error is emitted since asyn_axis is not a nullptr but also not an instance of C804Axis
|
||||
C804Axis *axis = dynamic_cast<C804Axis *>(asynAxis);
|
||||
if (axis == nullptr)
|
||||
{
|
||||
asynPrint(lowLevelPortUser_, ASYN_TRACE_ERROR, "%s: Axis %d is not a C804 Axis", functionName, axis->axisNo_);
|
||||
}
|
||||
return axis;
|
||||
}
|
||||
|
||||
/*
|
||||
Sends the given command to the axis specified by axisNo and returns the response
|
||||
of the axis.
|
||||
*/
|
||||
asynStatus C804Controller::lowLevelWriteRead(int axisNo, const char *command, char *response, bool expect_response)
|
||||
{
|
||||
// Definition of local variables.
|
||||
static const char *functionName = "C804Controller::lowLevelWriteRead";
|
||||
asynStatus status = asynSuccess;
|
||||
C804Axis *axis = getAxis(axisNo);
|
||||
|
||||
if (axis == nullptr)
|
||||
{
|
||||
// We already did the error logging directly in getAxis
|
||||
return asynError;
|
||||
}
|
||||
|
||||
// TBD: Is this interpretation correct?
|
||||
int eomReason = 0; // Flag indicating why the message has ended
|
||||
size_t nbytesOut = 0; // Number of bytes of the outgoing message (which is command + the end-of-string terminator defined in the constructor)
|
||||
size_t nbytesIn = 0; // Number of bytes of the incoming message (which is response + the end-of-string terminator defined in the constructor)
|
||||
|
||||
// If the class instance could not be connected to the device, set an error flag.
|
||||
if (lowLevelPortUser_ == nullptr)
|
||||
{
|
||||
asynPrint(this->lowLevelPortUser_, ASYN_TRACE_ERROR,
|
||||
"%s: not connected to C804 controller\n",
|
||||
functionName);
|
||||
|
||||
// Adjust the parameter library
|
||||
setIntegerParam(this->motorStatusCommsError_, 1);
|
||||
return asynError;
|
||||
}
|
||||
|
||||
// Mask ASYN_TRACEIO_DRIVER is defined as "Device support reports I/O activity"
|
||||
asynPrint(lowLevelPortUser_, ASYN_TRACEIO_DRIVER, "%s: command: %s\n", functionName, command);
|
||||
|
||||
// Writes the command to the port and blocks until a response has been received or until the timeout has been reached.
|
||||
// For some inputs (such as TP = Tell position), we expect a response which is terminated by a character array "x03"
|
||||
// Other messages such as MA100 (move) don't return a response
|
||||
if (expect_response)
|
||||
{
|
||||
status = pasynOctetSyncIO->writeRead(lowLevelPortUser_,
|
||||
command, strlen(command),
|
||||
response, this->C804_MAXBUF_,
|
||||
C804_TIMEOUT_,
|
||||
&nbytesOut, &nbytesIn, &eomReason);
|
||||
}
|
||||
else
|
||||
{
|
||||
status = pasynOctetSyncIO->write(lowLevelPortUser_,
|
||||
command, strlen(command),
|
||||
C804_TIMEOUT_,
|
||||
&nbytesOut);
|
||||
}
|
||||
|
||||
// Writing and/or reading succeded
|
||||
if (status == asynSuccess)
|
||||
{
|
||||
asynPrint(lowLevelPortUser_, ASYN_TRACEIO_DRIVER, "%s: device response: %s\n", functionName, response);
|
||||
|
||||
// Reset any error which might have been set
|
||||
setIntegerParam(this->motorStatusCommsError_, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
asynPrint(this->lowLevelPortUser_, ASYN_TRACE_ERROR,
|
||||
"%s: asynOctetSyncIO->writeRead failed for command %s on axis %d\n",
|
||||
functionName, command, axisNo);
|
||||
|
||||
setIntegerParam(this->motorStatusCommsError_, 1);
|
||||
}
|
||||
|
||||
// Block the thread to avoid sending too many messages in a short timeframe
|
||||
usleep(interMessageSleep);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/*************************************************************************************/
|
||||
/** The following functions are C-wrappers, and can be called directly from iocsh */
|
||||
|
||||
extern "C"
|
||||
{
|
||||
|
||||
/*
|
||||
C wrapper for the C804Controller constructor.
|
||||
*/
|
||||
asynStatus C804CreateController(const char *portName, const char *lowLevelPortName, int lowLevelPortAddress,
|
||||
int numAxes, double movingPollPeriod, double idlePollPeriod)
|
||||
{
|
||||
/*
|
||||
We create a new instance of C804CreateController, using the "new" keyword to allocate it
|
||||
on the heap while avoiding RAII.
|
||||
TBD: Where is the pointer to the controller stored?
|
||||
https://github.com/epics-modules/motor/blob/master/motorApp/MotorSrc/asynMotorController.cpp
|
||||
https://github.com/epics-modules/asyn/blob/master/asyn/asynPortDriver/asynPortDriver.cpp
|
||||
|
||||
Setting the pointer to nullptr / NULL immediately after construction is simply
|
||||
done to avoid compiler warnings, see page 7 of this document:
|
||||
https://subversion.xray.aps.anl.gov/synApps/measComp/trunk/documentation/measCompTutorial.pdf
|
||||
*/
|
||||
C804Controller *pController = new C804Controller(portName, lowLevelPortName, lowLevelPortAddress, numAxes, movingPollPeriod, idlePollPeriod);
|
||||
pController = nullptr;
|
||||
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
/*
|
||||
C wrapper for the C804Axis constructor.
|
||||
See C804Axis::C804Axis.
|
||||
*/
|
||||
asynStatus C804CreateAxis(const char *C804Name, int axis)
|
||||
{
|
||||
C804Axis *pAxis;
|
||||
|
||||
static const char *functionName = "C804CreateAxis";
|
||||
|
||||
/*
|
||||
findAsynPortDriver is a asyn library FFI function which uses the C ABI.
|
||||
Therefore it returns a void pointer instead of e.g. a pointer to a superclass
|
||||
of the controller such as asynPortDriver. Type-safe upcasting via dynamic_cast
|
||||
is therefore not possible directly. However, we do know that the void
|
||||
pointer is either a pointer to asynPortDriver (if a driver with the specified name exists)
|
||||
or a nullptr. Therefore, we first do a nullptr check, then a cast to asynPortDriver
|
||||
and lastly a (typesafe) dynamic_upcast to C804Controller
|
||||
https://stackoverflow.com/questions/70906749/is-there-a-safe-way-to-cast-void-to-class-pointer-in-c
|
||||
*/
|
||||
void *ptr = findAsynPortDriver(C804Name);
|
||||
if (ptr == nullptr)
|
||||
{
|
||||
/*
|
||||
We can't use asynPrint here since this macro would require us
|
||||
to get a lowLevelPortUser_ from a pointer to an asynPortDriver.
|
||||
However, the given pointer is a nullptr and therefore doesn't
|
||||
have a lowLevelPortUser_! printf is an EPICS alternative which
|
||||
works w/o that, but doesn't offer the comfort provided
|
||||
by the asynTrace-facility
|
||||
*/
|
||||
printf("%s:%s: Error port %s not found\n", driverName, functionName, C804Name);
|
||||
return asynError;
|
||||
}
|
||||
// Unsafe cast of the pointer to an asynPortDriver
|
||||
asynPortDriver *apd = (asynPortDriver *)(ptr);
|
||||
|
||||
// Safe downcast
|
||||
C804Controller *pC = dynamic_cast<C804Controller *>(apd);
|
||||
if (pC == nullptr)
|
||||
{
|
||||
printf("%s: controller on port %s is not a C804Controller\n", functionName, C804Name);
|
||||
return asynError;
|
||||
}
|
||||
|
||||
// Prevent manipulation of the controller from other threads while we create the new axis.
|
||||
pC->lock();
|
||||
|
||||
/*
|
||||
We create a new instance of C804Axis, using the "new" keyword to allocate it
|
||||
on the heap while avoiding RAII. In the constructor, a pointer to the new object is stored in
|
||||
the controller object "pC". Therefore, the axis instance can still be
|
||||
reached later by quering "pC".
|
||||
|
||||
Setting the pointer to nullptr / NULL immediately after construction is simply
|
||||
done to avoid compiler warnings, see page 7 of this document:
|
||||
https://subversion.xray.aps.anl.gov/synApps/measComp/trunk/documentation/measCompTutorial.pdf
|
||||
*/
|
||||
pAxis = new C804Axis(pC, axis);
|
||||
pAxis = nullptr;
|
||||
|
||||
// Allow manipulation of the controller again
|
||||
pC->unlock();
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
/*
|
||||
This is boilerplate code which is used to make the FFI functions
|
||||
C804CreateController and C804CreateAxis "known" to the IOC shell (iocsh).
|
||||
TBD: If the code is compiled for running on vxWorks, this registration is
|
||||
apparently not necessary?
|
||||
*/
|
||||
|
||||
#ifdef vxWorks
|
||||
#else
|
||||
|
||||
/*
|
||||
Define name and type of the arguments for the C804CreateController function
|
||||
in the iocsh. This is done by creating structs with the argument names and types
|
||||
and then providing "factory" functions (configC804CreateControllerCallFunc).
|
||||
These factory functions are used to register the constructors during compilation.
|
||||
*/
|
||||
static const iocshArg C804CreateControllerArg0 = {"Controller port name", iocshArgString};
|
||||
static const iocshArg C804CreateControllerArg1 = {"Low level port name", iocshArgString};
|
||||
static const iocshArg C804CreateControllerArg2 = {"Low level port address", iocshArgInt};
|
||||
static const iocshArg C804CreateControllerArg3 = {"Number of axes", iocshArgInt};
|
||||
static const iocshArg C804CreateControllerArg4 = {"Moving poll rate (s)", iocshArgDouble};
|
||||
static const iocshArg C804CreateControllerArg5 = {"Idle poll rate (s)", iocshArgDouble};
|
||||
static const iocshArg *const C804CreateControllerArgs[] = {&C804CreateControllerArg0,
|
||||
&C804CreateControllerArg1,
|
||||
&C804CreateControllerArg2,
|
||||
&C804CreateControllerArg3,
|
||||
&C804CreateControllerArg4,
|
||||
&C804CreateControllerArg5};
|
||||
static const iocshFuncDef configC804CreateController = {"C804CreateController", 6, C804CreateControllerArgs};
|
||||
static void configC804CreateControllerCallFunc(const iocshArgBuf *args)
|
||||
{
|
||||
C804CreateController(args[0].sval, args[1].sval, args[2].ival, args[3].ival, args[4].dval, args[5].dval);
|
||||
}
|
||||
|
||||
/*
|
||||
Same procedure as for the C804CreateController function, but for the axis itself.
|
||||
*/
|
||||
static const iocshArg C804CreateAxisArg0 = {"Controller port name", iocshArgString};
|
||||
static const iocshArg C804CreateAxisArg1 = {"Axis number", iocshArgInt};
|
||||
static const iocshArg *const C804CreateAxisArgs[] = {&C804CreateAxisArg0,
|
||||
&C804CreateAxisArg1};
|
||||
static const iocshFuncDef configC804CreateAxis = {"C804CreateAxis", 2, C804CreateAxisArgs};
|
||||
static void configC804CreateAxisCallFunc(const iocshArgBuf *args)
|
||||
{
|
||||
C804CreateAxis(args[0].sval, args[1].ival);
|
||||
}
|
||||
|
||||
// This function is made known to EPICS in sinq.dbd and is called by EPICS
|
||||
// in order to register both functions in the IOC shell
|
||||
// TBD: Does this happen during compilation?
|
||||
static void C804ControllerRegister(void)
|
||||
{
|
||||
iocshRegister(&configC804CreateController, configC804CreateControllerCallFunc);
|
||||
iocshRegister(&configC804CreateAxis, configC804CreateAxisCallFunc);
|
||||
}
|
||||
epicsExportRegistrar(C804ControllerRegister);
|
||||
|
||||
#endif
|
||||
|
||||
} // extern "C"
|
50
sinqEPICSApp/src/C804Controller.h
Normal file
50
sinqEPICSApp/src/C804Controller.h
Normal file
@ -0,0 +1,50 @@
|
||||
#ifndef C804Controller_H
|
||||
#define C804Controller_H
|
||||
|
||||
#include "SINQController.h"
|
||||
#include "C804Axis.h"
|
||||
#include "asynMotorAxis.h"
|
||||
|
||||
class C804Controller : public SINQController
|
||||
{
|
||||
public:
|
||||
C804Controller(const char *portName, const char *lowLevelPortName, int lowLevelPortAddress, int numAxes, double movingPollPeriod,
|
||||
double idlePollPeriod, const int &extraParams = 2);
|
||||
|
||||
virtual ~C804Controller();
|
||||
|
||||
/* These are the methods that we override */
|
||||
C804Axis *getAxis(asynUser *pasynUser);
|
||||
C804Axis *getAxis(int axisNo);
|
||||
C804Axis *castToC804Axis(asynMotorAxis *asynAxis);
|
||||
|
||||
protected:
|
||||
asynUser *lowLevelPortUser_;
|
||||
|
||||
// User-defined polling periods in ms
|
||||
time_t movingPollPeriod_;
|
||||
time_t idlePollPeriod_;
|
||||
|
||||
void log(const char *message);
|
||||
asynStatus lowLevelWriteRead(int axisNo, const char *command, char *response, bool expect_response);
|
||||
|
||||
private:
|
||||
// Set the maximum buffer size. This is an empirical value which must be large
|
||||
// enough to avoid overflows for all commands to the device / responses from it.
|
||||
static const uint32_t C804_MAXBUF_ = 200;
|
||||
|
||||
/*
|
||||
When trying to communicate with the device, the underlying asynOctetSyncIO
|
||||
interface waits for a response until this time (in seconds) has passed,
|
||||
then it declares a timeout. This variable has to be specified in the .cpp-file.
|
||||
Tying to specify in the .h-file results in the following compiler error:
|
||||
In file included from ../sinqEPICSApp/src/C804Axis.cpp:2:
|
||||
../sinqEPICSApp/src/C804Controller.h:38:23: error: ‘constexpr’ needed for in-class initialization
|
||||
of static data member ‘const double C804Controller::C804_TIMEOUT_’ of non-integral type
|
||||
*/
|
||||
static const double C804_TIMEOUT_;
|
||||
|
||||
friend class C804Axis;
|
||||
};
|
||||
|
||||
#endif
|
@ -11,7 +11,6 @@ Mark Koennecke, May, August 2017
|
||||
|
||||
*/
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
@ -30,115 +29,111 @@ Mark Koennecke, May, August 2017
|
||||
|
||||
#define IDLEPOLL 60
|
||||
|
||||
|
||||
/** Creates a new EL734Controller object.
|
||||
* \param[in] portName The name of the asyn port that will be created for this driver
|
||||
* \param[in] EL734PortName The name of the drvAsynSerialPort that was created previously to connect to the EL734 controller
|
||||
* \param[in] numAxes The number of axes that this controller supports
|
||||
*/
|
||||
* \param[in] portName The name of the asyn port that will be created for this driver
|
||||
* \param[in] EL734PortName The name of the drvAsynSerialPort that was created previously to connect to the EL734 controller
|
||||
* \param[in] numAxes The number of axes that this controller supports
|
||||
*/
|
||||
EL734Controller::EL734Controller(const char *portName, const char *EL734PortName, int numAxes)
|
||||
: SINQController(portName, EL734PortName, numAxes)
|
||||
: SINQController(portName, EL734PortName, numAxes)
|
||||
{
|
||||
int axis;
|
||||
asynStatus status;
|
||||
EL734Axis *pAxis;
|
||||
static const char *functionName = "EL734Controller::EL734Controller";
|
||||
|
||||
|
||||
/* Connect to EL734 controller */
|
||||
status = pasynOctetSyncIO->connect(EL734PortName, 0, &pasynUserController_, NULL);
|
||||
if (status) {
|
||||
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"%s: cannot connect to EL734 controller\n",
|
||||
functionName);
|
||||
if (status)
|
||||
{
|
||||
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"%s: cannot connect to EL734 controller\n",
|
||||
functionName);
|
||||
}
|
||||
pasynOctetSyncIO->setOutputEos(pasynUserController_,"\r",strlen("\r"));
|
||||
pasynOctetSyncIO->setInputEos(pasynUserController_,"\r",strlen("\r"));
|
||||
pasynOctetSyncIO->setOutputEos(pasynUserController_, "\r", strlen("\r"));
|
||||
pasynOctetSyncIO->setInputEos(pasynUserController_, "\r", strlen("\r"));
|
||||
|
||||
switchRemote();
|
||||
|
||||
for (axis=0; axis<numAxes; axis++) {
|
||||
pAxis = new EL734Axis(this, axis+1);
|
||||
for (axis = 0; axis < numAxes; axis++)
|
||||
{
|
||||
new EL734Axis(this, axis + 1);
|
||||
}
|
||||
|
||||
startPoller(1000./1000., IDLEPOLL, 2);
|
||||
startPoller(1000. / 1000., IDLEPOLL, 2);
|
||||
}
|
||||
|
||||
|
||||
/** Creates a new EL734Controller object.
|
||||
* Configuration command, called directly or from iocsh
|
||||
* \param[in] portName The name of the asyn port that will be created for this driver
|
||||
* \param[in] EL734PortName The name of the drvAsynIPPPort that was created previously to connect to the EL734 controller
|
||||
* \param[in] numAxes The number of axes that this controller supports
|
||||
*/
|
||||
* Configuration command, called directly or from iocsh
|
||||
* \param[in] portName The name of the asyn port that will be created for this driver
|
||||
* \param[in] EL734PortName The name of the drvAsynIPPPort that was created previously to connect to the EL734 controller
|
||||
* \param[in] numAxes The number of axes that this controller supports
|
||||
*/
|
||||
extern "C" int EL734CreateController(const char *portName, const char *EL734PortName, int numAxes)
|
||||
{
|
||||
EL734Controller *pEL734Controller
|
||||
= new EL734Controller(portName, EL734PortName, numAxes);
|
||||
EL734Controller *pEL734Controller = new EL734Controller(portName, EL734PortName, numAxes);
|
||||
pEL734Controller = NULL;
|
||||
return(asynSuccess);
|
||||
return (asynSuccess);
|
||||
}
|
||||
|
||||
/** Reports on status of the driver
|
||||
* \param[in] fp The file pointer on which report information will be written
|
||||
* \param[in] level The level of report detail desired
|
||||
*
|
||||
* If details > 0 then information is printed about each axis.
|
||||
* After printing controller-specific information it calls asynMotorController::report()
|
||||
*/
|
||||
* \param[in] fp The file pointer on which report information will be written
|
||||
* \param[in] level The level of report detail desired
|
||||
*
|
||||
* If details > 0 then information is printed about each axis.
|
||||
* After printing controller-specific information it calls asynMotorController::report()
|
||||
*/
|
||||
void EL734Controller::report(FILE *fp, int level)
|
||||
{
|
||||
fprintf(fp, "EL734 motor driver %s, numAxes=%d\n",
|
||||
this->portName, numAxes_);
|
||||
fprintf(fp, "EL734 motor driver %s, numAxes=%d\n",
|
||||
this->portName, numAxes_);
|
||||
|
||||
// Call the base class method
|
||||
asynMotorController::report(fp, level);
|
||||
}
|
||||
|
||||
/** Returns a pointer to an EL734Axis object.
|
||||
* Returns NULL if the axis number encoded in pasynUser is invalid.
|
||||
* \param[in] pasynUser asynUser structure that encodes the axis index number. */
|
||||
EL734Axis* EL734Controller::getAxis(asynUser *pasynUser)
|
||||
* Returns NULL if the axis number encoded in pasynUser is invalid.
|
||||
* \param[in] pasynUser asynUser structure that encodes the axis index number. */
|
||||
EL734Axis *EL734Controller::getAxis(asynUser *pasynUser)
|
||||
{
|
||||
return static_cast<EL734Axis*>(asynMotorController::getAxis(pasynUser));
|
||||
return static_cast<EL734Axis *>(asynMotorController::getAxis(pasynUser));
|
||||
}
|
||||
|
||||
/** Returns a pointer to an EL734Axis object.
|
||||
* Returns NULL if the axis number encoded in pasynUser is invalid.
|
||||
* \param[in] axisNo Axis index number. */
|
||||
EL734Axis* EL734Controller::getAxis(int axisNo)
|
||||
* Returns NULL if the axis number encoded in pasynUser is invalid.
|
||||
* \param[in] axisNo Axis index number. */
|
||||
EL734Axis *EL734Controller::getAxis(int axisNo)
|
||||
{
|
||||
return static_cast<EL734Axis*>(asynMotorController::getAxis(axisNo));
|
||||
return static_cast<EL734Axis *>(asynMotorController::getAxis(axisNo));
|
||||
}
|
||||
|
||||
void EL734Controller::switchRemote()
|
||||
{
|
||||
char command[COMLEN], reply[COMLEN];
|
||||
int status;
|
||||
size_t in, out;
|
||||
int reason;
|
||||
|
||||
strcpy(command,"RMT 1");
|
||||
status = pasynOctetSyncIO->writeRead(pasynUserController_, command, strlen(command),
|
||||
reply,COMLEN, 1.,&out,&in,&reason);
|
||||
strcpy(command,"ECHO 0");
|
||||
status = pasynOctetSyncIO->writeRead(pasynUserController_, command, strlen(command),
|
||||
reply,COMLEN, 1.,&out,&in,&reason);
|
||||
strcpy(command,"RMT 1");
|
||||
status = pasynOctetSyncIO->writeRead(pasynUserController_, command, strlen(command),
|
||||
reply,COMLEN, 1.,&out,&in,&reason);
|
||||
strcpy(command,"ECHO 0");
|
||||
status = pasynOctetSyncIO->writeRead(pasynUserController_, command, strlen(command),
|
||||
reply,COMLEN, 1.,&out,&in,&reason);
|
||||
strcpy(command, "RMT 1");
|
||||
pasynOctetSyncIO->writeRead(pasynUserController_, command, strlen(command),
|
||||
reply, COMLEN, 1., &out, &in, &reason);
|
||||
strcpy(command, "ECHO 0");
|
||||
pasynOctetSyncIO->writeRead(pasynUserController_, command, strlen(command),
|
||||
reply, COMLEN, 1., &out, &in, &reason);
|
||||
strcpy(command, "RMT 1");
|
||||
pasynOctetSyncIO->writeRead(pasynUserController_, command, strlen(command),
|
||||
reply, COMLEN, 1., &out, &in, &reason);
|
||||
strcpy(command, "ECHO 0");
|
||||
pasynOctetSyncIO->writeRead(pasynUserController_, command, strlen(command),
|
||||
reply, COMLEN, 1., &out, &in, &reason);
|
||||
}
|
||||
/**
|
||||
* send a command to the EL734 and read the reply. Do some error and controller
|
||||
* send a command to the EL734 and read the reply. Do some error and controller
|
||||
* issue fixing on the way
|
||||
* \param[in] command The command to send
|
||||
* \param[out] reply The controllers reply
|
||||
*/
|
||||
|
||||
asynStatus EL734Controller::transactController(int axisNo,char command[COMLEN], char reply[COMLEN])
|
||||
asynStatus EL734Controller::transactController(int axisNo, char command[COMLEN], char reply[COMLEN])
|
||||
{
|
||||
asynStatus status;
|
||||
size_t in, out, i;
|
||||
@ -147,58 +142,76 @@ asynStatus EL734Controller::transactController(int axisNo,char command[COMLEN],
|
||||
SINQAxis *axis = getAxis(axisNo);
|
||||
|
||||
pasynOctetSyncIO->flush(pasynUserController_);
|
||||
|
||||
|
||||
if (axis != NULL)
|
||||
{
|
||||
axis->updateMsgTxtFromDriver("");
|
||||
}
|
||||
|
||||
status = pasynOctetSyncIO->writeRead(pasynUserController_, command, strlen(command),
|
||||
reply,COMLEN, 1.,&out,&in,&reason);
|
||||
if(status != asynSuccess){
|
||||
if(axis!= NULL){
|
||||
reply, COMLEN, 2., &out, &in, &reason);
|
||||
if (status != asynSuccess)
|
||||
{
|
||||
if (axis != NULL)
|
||||
{
|
||||
axis->updateMsgTxtFromDriver("Lost connection to motor controller");
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
check for the offline reply
|
||||
*/
|
||||
if(strstr(reply,"?LOC") != NULL){
|
||||
if (strstr(reply, "?LOC") != NULL)
|
||||
{
|
||||
switchRemote();
|
||||
return pasynOctetSyncIO->writeRead(pasynUserController_, command, strlen(command),
|
||||
reply,COMLEN, 1.,&out,&in,&reason);
|
||||
reply, COMLEN, 1., &out, &in, &reason);
|
||||
}
|
||||
|
||||
/*
|
||||
check for echos. This means that the thing is offline.
|
||||
*/
|
||||
if(strstr(reply,"p ") != NULL || strstr(reply,"u ") != NULL || strstr(reply,"msr ") != NULL){
|
||||
if (strstr(reply, "p ") != NULL || strstr(reply, "u ") != NULL || strstr(reply, "msr ") != NULL)
|
||||
{
|
||||
switchRemote();
|
||||
return pasynOctetSyncIO->writeRead(pasynUserController_, command, strlen(command),
|
||||
reply,COMLEN, 1.,&out,&in,&reason);
|
||||
reply, COMLEN, 1., &out, &in, &reason);
|
||||
}
|
||||
/*
|
||||
check for EL734 errors
|
||||
*/
|
||||
strcpy(myReply, reply);
|
||||
for(i = 0; i < strlen(reply); i++){
|
||||
for (i = 0; i < strlen(reply); i++)
|
||||
{
|
||||
myReply[i] = (char)tolower((int)reply[i]);
|
||||
}
|
||||
if(strstr(myReply,"?cmd") != NULL){
|
||||
snprintf(errTxt,sizeof(errTxt), "Bad command %s at axis %d", command, axisNo);
|
||||
errlogSevPrintf(errlogMajor, errTxt);
|
||||
if(axis!= NULL){
|
||||
if (strstr(myReply, "?cmd") != NULL)
|
||||
{
|
||||
snprintf(errTxt, sizeof(errTxt), "Bad command %s at axis %d", command, axisNo);
|
||||
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, errTxt);
|
||||
if (axis != NULL)
|
||||
{
|
||||
axis->updateMsgTxtFromDriver(errTxt);
|
||||
}
|
||||
return asynError;
|
||||
} else if(strstr(myReply,"?par") != NULL){
|
||||
snprintf(errTxt,sizeof(errTxt), "Bad parameter in command %s", command);
|
||||
errlogSevPrintf(errlogMajor, errTxt);
|
||||
if(axis!= NULL){
|
||||
}
|
||||
else if (strstr(myReply, "?par") != NULL)
|
||||
{
|
||||
snprintf(errTxt, sizeof(errTxt), "Bad parameter in command %s", command);
|
||||
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, errTxt);
|
||||
if (axis != NULL)
|
||||
{
|
||||
axis->updateMsgTxtFromDriver(errTxt);
|
||||
}
|
||||
return asynError;
|
||||
} else if(strstr(myReply,"?rng") != NULL){
|
||||
snprintf(errTxt,sizeof(errTxt), "Parameter out of range in command %s", command);
|
||||
errlogSevPrintf(errlogMajor, errTxt);
|
||||
if(axis!= NULL){
|
||||
}
|
||||
else if (strstr(myReply, "?rng") != NULL)
|
||||
{
|
||||
snprintf(errTxt, sizeof(errTxt), "Parameter out of range in command %s", command);
|
||||
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, errTxt);
|
||||
if (axis != NULL)
|
||||
{
|
||||
axis->updateMsgTxtFromDriver(errTxt);
|
||||
}
|
||||
return asynError;
|
||||
@ -210,51 +223,99 @@ asynStatus EL734Controller::transactController(int axisNo,char command[COMLEN],
|
||||
// These are the EL734Axis methods
|
||||
|
||||
/** Creates a new EL734Axis object.
|
||||
* \param[in] pC Pointer to the EL734Controller to which this axis belongs.
|
||||
* \param[in] axisNo Index number of this axis, range 0 to pC->numAxes_-1.
|
||||
*
|
||||
* Initializes register numbers, etc.
|
||||
*/
|
||||
* \param[in] pC Pointer to the EL734Controller to which this axis belongs.
|
||||
* \param[in] axisNo Index number of this axis, range 0 to pC->numAxes_-1.
|
||||
*
|
||||
* Initializes register numbers, etc.
|
||||
*/
|
||||
EL734Axis::EL734Axis(EL734Controller *pC, int axisNo)
|
||||
: SINQAxis(pC, axisNo), pC_(pC)
|
||||
{
|
||||
: SINQAxis(pC, axisNo), pC_(pC)
|
||||
{
|
||||
char command[COMLEN], reply[COMLEN];
|
||||
asynStatus status;
|
||||
int count = 0;
|
||||
float low, high;
|
||||
|
||||
/*
|
||||
get the hardware limits from the controller
|
||||
*/
|
||||
sprintf(command, "H %d", axisNo_);
|
||||
status = pC_->transactController(axisNo_, command, reply);
|
||||
if (status == asynSuccess)
|
||||
{
|
||||
count = sscanf(reply, "%f %f", &low, &high);
|
||||
if (count >= 2)
|
||||
{
|
||||
pC_->setDoubleParam(axisNo_, pC_->motorLowLimit_, low);
|
||||
pC_->setDoubleParam(axisNo_, pC_->motorHighLimit_, high);
|
||||
callParamCallbacks();
|
||||
}
|
||||
else
|
||||
{
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"Bad response - %s - requesting limits at axis %d", reply, axisNo_);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"Failed to read limits at axis %d", axisNo_);
|
||||
}
|
||||
|
||||
/*
|
||||
oredMSR contains the axis status from the previous poll. The initial value
|
||||
1 means that the axis is not moving and has no errors.
|
||||
*/
|
||||
oredMSR = 1;
|
||||
next_poll = -1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** Reports on status of the axis
|
||||
* \param[in] fp The file pointer on which report information will be written
|
||||
* \param[in] level The level of report detail desired
|
||||
*
|
||||
* After printing device-specific information calls asynMotorAxis::report()
|
||||
*/
|
||||
* \param[in] fp The file pointer on which report information will be written
|
||||
* \param[in] level The level of report detail desired
|
||||
*
|
||||
* After printing device-specific information calls asynMotorAxis::report()
|
||||
*/
|
||||
void EL734Axis::report(FILE *fp, int level)
|
||||
{
|
||||
if (level > 0) {
|
||||
if (level > 0)
|
||||
{
|
||||
fprintf(fp, " axis %d\n",
|
||||
axisNo_);
|
||||
}
|
||||
|
||||
// Call the base class method
|
||||
//asynMotorAxis::report(fp, level);
|
||||
// asynMotorAxis::report(fp, level);
|
||||
}
|
||||
|
||||
|
||||
asynStatus EL734Axis::move(double position, int relative, double minVelocity, double maxVelocity, double acceleration)
|
||||
{
|
||||
asynStatus status;
|
||||
//static const char *functionName = "EL734Axis::move";
|
||||
// static const char *functionName = "EL734Axis::move";
|
||||
char command[COMLEN], reply[COMLEN];
|
||||
|
||||
// status = sendAccelAndVelocity(acceleration, maxVelocity);
|
||||
|
||||
if (relative) {
|
||||
|
||||
errlogPrintf("minVelocity = %f, maxVelocity = %f\n", minVelocity, maxVelocity);
|
||||
|
||||
/*
|
||||
* set speed
|
||||
*/
|
||||
sprintf(command, "J %d %d", axisNo_, (int)maxVelocity);
|
||||
status = pC_->transactController(axisNo_, command, reply);
|
||||
|
||||
if (relative)
|
||||
{
|
||||
position += this->position;
|
||||
}
|
||||
oredMSR = 0;
|
||||
homing = 0;
|
||||
sprintf(command, "p %d %.3f", axisNo_, position/1000.);
|
||||
status = pC_->transactController(axisNo_,command,reply);
|
||||
errorReported = 0;
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"Starting axis %d with destination %f", axisNo_, position / 1000);
|
||||
sprintf(command, "p %d %.3f", axisNo_, position / 1000.);
|
||||
status = pC_->transactController(axisNo_, command, reply);
|
||||
setIntegerParam(pC_->motorStatusProblem_, false);
|
||||
updateMsgTxtFromDriver("");
|
||||
next_poll = -1;
|
||||
return status;
|
||||
}
|
||||
@ -262,51 +323,66 @@ asynStatus EL734Axis::move(double position, int relative, double minVelocity, do
|
||||
asynStatus EL734Axis::home(double minVelocity, double maxVelocity, double acceleration, int forwards)
|
||||
{
|
||||
asynStatus status;
|
||||
//static const char *functionName = "EL734Axis::home";
|
||||
// static const char *functionName = "EL734Axis::home";
|
||||
char command[COMLEN], reply[COMLEN];
|
||||
|
||||
// status = sendAccelAndVelocity(acceleration, maxVelocity);
|
||||
|
||||
setIntegerParam(pC_->motorStatusProblem_, false);
|
||||
updateMsgTxtFromDriver("");
|
||||
errorReported = 0;
|
||||
|
||||
sprintf(command, "R %d", axisNo_);
|
||||
homing = 1;
|
||||
next_poll= -1;
|
||||
status = pC_->transactController(axisNo_,command,reply);
|
||||
next_poll = -1;
|
||||
status = pC_->transactController(axisNo_, command, reply);
|
||||
return status;
|
||||
}
|
||||
|
||||
asynStatus EL734Axis::moveVelocity(double minVelocity, double maxVelocity, double acceleration)
|
||||
{
|
||||
asynStatus status;
|
||||
//static const char *functionName = "EL734Axis::moveVelocity";
|
||||
// static const char *functionName = "EL734Axis::moveVelocity";
|
||||
char command[COMLEN], reply[COMLEN];
|
||||
|
||||
// asynPrint(pasynUser_, ASYN_TRACE_FLOW,
|
||||
// "%s: minVelocity=%f, maxVelocity=%f, acceleration=%f\n",
|
||||
// functionName, minVelocity, maxVelocity, acceleration);
|
||||
|
||||
|
||||
if (maxVelocity > 0.) {
|
||||
errorReported = 0;
|
||||
if (maxVelocity > 0.)
|
||||
{
|
||||
/* This is a positive move */
|
||||
sprintf(command, "FF %d", axisNo_);
|
||||
} else {
|
||||
/* This is a negative move */
|
||||
sprintf(command, "FB %d", axisNo_);
|
||||
}
|
||||
status = pC_->transactController(axisNo_,command,reply);
|
||||
else
|
||||
{
|
||||
/* This is a negative move */
|
||||
sprintf(command, "FB %d", axisNo_);
|
||||
}
|
||||
status = pC_->transactController(axisNo_, command, reply);
|
||||
setIntegerParam(pC_->motorStatusProblem_, false);
|
||||
updateMsgTxtFromDriver("");
|
||||
next_poll = -1;
|
||||
return status;
|
||||
}
|
||||
|
||||
asynStatus EL734Axis::stop(double acceleration )
|
||||
asynStatus EL734Axis::stop(double acceleration)
|
||||
{
|
||||
asynStatus status;
|
||||
//static const char *functionName = "EL734Axis::stop";
|
||||
asynStatus status = asynSuccess;
|
||||
// static const char *functionName = "EL734Axis::stop";
|
||||
char command[COMLEN], reply[COMLEN];
|
||||
bool moving = false;
|
||||
|
||||
sprintf(command, "S %d", axisNo_);
|
||||
status = pC_->transactController(axisNo_,command,reply);
|
||||
errlogPrintf("Sent STOP on Axis %d\n", axisNo_);
|
||||
updateMsgTxtFromDriver("Axis interrupted");
|
||||
this->poll(&moving);
|
||||
if (moving && errorReported == 0)
|
||||
{
|
||||
sprintf(command, "S %d", axisNo_);
|
||||
status = pC_->transactController(axisNo_, command, reply);
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR, "Sent STOP on Axis %d\n", axisNo_);
|
||||
updateMsgTxtFromDriver("Axis interrupted");
|
||||
errorReported = 1;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
@ -314,11 +390,11 @@ asynStatus EL734Axis::stop(double acceleration )
|
||||
asynStatus EL734Axis::setPosition(double position)
|
||||
{
|
||||
asynStatus status;
|
||||
//static const char *functionName = "EL734Axis::setPosition";
|
||||
// static const char *functionName = "EL734Axis::setPosition";
|
||||
char command[COMLEN], reply[COMLEN];
|
||||
|
||||
sprintf(command, "P %d %f", axisNo_, position/1000.);
|
||||
status = pC_->transactController(axisNo_,command,reply);
|
||||
sprintf(command, "U %d %f", axisNo_, position / 1000.);
|
||||
status = pC_->transactController(axisNo_, command, reply);
|
||||
next_poll = -1;
|
||||
|
||||
return status;
|
||||
@ -326,8 +402,8 @@ asynStatus EL734Axis::setPosition(double position)
|
||||
|
||||
asynStatus EL734Axis::setClosedLoop(bool closedLoop)
|
||||
{
|
||||
//static const char *functionName = "EL734Axis::setClosedLoop";
|
||||
|
||||
// static const char *functionName = "EL734Axis::setClosedLoop";
|
||||
|
||||
/*
|
||||
This belongs into the Kingdom of Electronics.
|
||||
We do not do this.
|
||||
@ -337,126 +413,188 @@ asynStatus EL734Axis::setClosedLoop(bool closedLoop)
|
||||
}
|
||||
|
||||
/** Polls the axis.
|
||||
* This function reads the motor position, the limit status, the home status, the moving status,
|
||||
* and the drive power-on status.
|
||||
* It calls setIntegerParam() and setDoubleParam() for each item that it polls,
|
||||
* and then calls callParamCallbacks() at the end.
|
||||
* \param[out] moving A flag that is set indicating that the axis is moving (true) or done (false). */
|
||||
* This function reads the motor position, the limit status, the home status, the moving status,
|
||||
* and the drive power-on status.
|
||||
* It calls setIntegerParam() and setDoubleParam() for each item that it polls,
|
||||
* and then calls callParamCallbacks() at the end.
|
||||
* \param[out] moving A flag that is set indicating that the axis is moving (true) or done (false). */
|
||||
asynStatus EL734Axis::poll(bool *moving)
|
||||
{
|
||||
{
|
||||
int msr, count;
|
||||
asynStatus comStatus;
|
||||
float low, high;
|
||||
asynStatus comStatus = asynSuccess;
|
||||
char command[COMLEN], reply[COMLEN], errTxt[256];
|
||||
int driverError = 0;
|
||||
|
||||
|
||||
// protect against excessive polling
|
||||
if(time(NULL) < next_poll){
|
||||
if (time(NULL) < next_poll)
|
||||
{
|
||||
*moving = false;
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
// Read the current motor position
|
||||
sprintf(command,"u %d", axisNo_);
|
||||
comStatus = pC_->transactController(axisNo_,command,reply);
|
||||
if(comStatus == asynError){
|
||||
driverError = 1;
|
||||
// read hardware limits
|
||||
sprintf(command, "H %d", axisNo_);
|
||||
comStatus = pC_->transactController(axisNo_, command, reply);
|
||||
if (comStatus == asynSuccess)
|
||||
{
|
||||
count = sscanf(reply, "%f %f", &low, &high);
|
||||
if (count >= 2)
|
||||
{
|
||||
pC_->setDoubleParam(axisNo_, pC_->motorLowLimit_, low);
|
||||
pC_->setDoubleParam(axisNo_, pC_->motorHighLimit_, high);
|
||||
callParamCallbacks();
|
||||
}
|
||||
}
|
||||
if(comStatus) goto skip;
|
||||
if(strstr(reply,"*ES") != NULL){
|
||||
|
||||
// Read the current motor position
|
||||
setIntegerParam(pC_->motorStatusProblem_, false);
|
||||
sprintf(command, "u %d", axisNo_);
|
||||
comStatus = pC_->transactController(axisNo_, command, reply);
|
||||
if (comStatus == asynError)
|
||||
{
|
||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
goto skip;
|
||||
}
|
||||
if (strstr(reply, "*ES") != NULL)
|
||||
{
|
||||
*moving = false;
|
||||
setIntegerParam(pC_->motorStatusDone_, true);
|
||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
errorReported = 1;
|
||||
updateMsgTxtFromDriver("Emergency Stop Engaged");
|
||||
driverError = 1;
|
||||
comStatus = asynError;
|
||||
goto skip;
|
||||
} else if(strstr(reply,"?BSY") != NULL){
|
||||
}
|
||||
else if (strstr(reply, "?BSY") != NULL)
|
||||
{
|
||||
*moving = true;
|
||||
setIntegerParam(pC_->motorStatusDone_, false);
|
||||
updateMsgTxtFromDriver(NULL);
|
||||
goto skip;
|
||||
}
|
||||
count = sscanf(reply,"%lf", &position);
|
||||
if(count != 1) {
|
||||
snprintf(errTxt,sizeof(errTxt),"Bad reply %s when reading position for %d", reply, axisNo_);
|
||||
updateMsgTxtFromDriver(errTxt);
|
||||
comStatus = asynError;
|
||||
driverError =1;
|
||||
goto skip;
|
||||
count = sscanf(reply, "%lf", &position);
|
||||
if (count != 1)
|
||||
{
|
||||
if (!homing)
|
||||
{
|
||||
snprintf(errTxt, sizeof(errTxt), "Bad reply %s when reading position for %d", reply, axisNo_);
|
||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
errorReported = 1;
|
||||
updateMsgTxtFromDriver(errTxt);
|
||||
comStatus = asynError;
|
||||
goto skip;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"Axis %d, reply %s, position %lf\n", axisNo_, reply, position);
|
||||
setDoubleParam(pC_->motorPosition_, position * 1000);
|
||||
// setDoubleParam(pC_->motorEncoderPosition_, position);
|
||||
}
|
||||
//errlogPrintf("Axis %d, reply %s, position %lf\n", axisNo_, reply, position);
|
||||
setDoubleParam(pC_->motorPosition_, position*1000);
|
||||
//setDoubleParam(pC_->motorEncoderPosition_, position);
|
||||
|
||||
|
||||
// Read the moving status of this motor
|
||||
sprintf(command,"msr %d",axisNo_);
|
||||
comStatus = pC_->transactController(axisNo_,command,reply);
|
||||
if(comStatus) goto skip;
|
||||
sscanf(reply,"%x",&msr);
|
||||
// errlogPrintf("Axis %d, reply %s, msr %d, position = %lf\n",
|
||||
// axisNo_, reply, msr, position);
|
||||
sprintf(command, "msr %d", axisNo_);
|
||||
comStatus = pC_->transactController(axisNo_, command, reply);
|
||||
if (comStatus == asynError)
|
||||
{
|
||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
goto skip;
|
||||
}
|
||||
sscanf(reply, "%x", &msr);
|
||||
|
||||
// errlogPrintf("Axis %d, reply %s, msr %d, oredmsr = %d, position = %lf\n",
|
||||
// axisNo_, reply, msr, oredMSR, position);
|
||||
|
||||
oredMSR |= msr;
|
||||
if( (msr & 0x1) == 0){
|
||||
if ((msr & 0x1) == 0)
|
||||
{
|
||||
// done: check for trouble
|
||||
//errlogPrintf("Axis %d finished\n", axisNo_);
|
||||
next_poll = time(NULL)+IDLEPOLL;
|
||||
if(oredMSR & 0x10){
|
||||
setIntegerParam(pC_->motorStatusLowLimit_, true);
|
||||
updateMsgTxtFromDriver("Lower Limit Hit");
|
||||
driverError = 1;
|
||||
} else {
|
||||
setIntegerParam(pC_->motorStatusLowLimit_, false);
|
||||
}
|
||||
if(oredMSR & 0x20){
|
||||
setIntegerParam(pC_->motorStatusHighLimit_, true);
|
||||
updateMsgTxtFromDriver("Upper Limit Hit");
|
||||
driverError = 1;
|
||||
} else {
|
||||
setIntegerParam(pC_->motorStatusHighLimit_, false);
|
||||
}
|
||||
if(homing){
|
||||
setIntegerParam(pC_->motorStatusAtHome_, true);
|
||||
}
|
||||
setIntegerParam(pC_->motorStatusProblem_, false);
|
||||
if(oredMSR &0x1000){
|
||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
errlogSevPrintf(errlogMajor, "Air cushion problem on %d", axisNo_);
|
||||
updateMsgTxtFromDriver("Air cushion error");
|
||||
driverError = 1;
|
||||
}
|
||||
if(oredMSR &0x80){
|
||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
errlogSevPrintf(errlogMajor, "Positioning fault at %d", axisNo_);
|
||||
updateMsgTxtFromDriver("Positioning fault");
|
||||
driverError = 1;
|
||||
}
|
||||
// errlogPrintf("Axis %d finished\n", axisNo_);
|
||||
*moving = false;
|
||||
setIntegerParam(pC_->motorStatusDone_, true);
|
||||
//updateMsgTxtFromDriver("Believed to be on position");
|
||||
} else {
|
||||
|
||||
next_poll = time(NULL) + IDLEPOLL;
|
||||
if (oredMSR & 0x10)
|
||||
{
|
||||
setIntegerParam(pC_->motorStatusLowLimit_, true);
|
||||
updateMsgTxtFromDriver("Lower Limit Hit");
|
||||
errorReported = 1;
|
||||
comStatus = asynError;
|
||||
goto skip;
|
||||
}
|
||||
else
|
||||
{
|
||||
setIntegerParam(pC_->motorStatusLowLimit_, false);
|
||||
}
|
||||
if (oredMSR & 0x20)
|
||||
{
|
||||
setIntegerParam(pC_->motorStatusHighLimit_, true);
|
||||
updateMsgTxtFromDriver("Upper Limit Hit");
|
||||
errorReported = 1;
|
||||
comStatus = asynError;
|
||||
goto skip;
|
||||
}
|
||||
else
|
||||
{
|
||||
setIntegerParam(pC_->motorStatusHighLimit_, false);
|
||||
}
|
||||
if (homing)
|
||||
{
|
||||
setIntegerParam(pC_->motorStatusAtHome_, true);
|
||||
}
|
||||
if (oredMSR & 0x1000)
|
||||
{
|
||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
// errlogPrintf("Detected air cushion error on %d", axisNo_);
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR, "Air cushion problem on %d", axisNo_);
|
||||
updateMsgTxtFromDriver("Air cushion error");
|
||||
errorReported = 1;
|
||||
comStatus = asynError;
|
||||
goto skip;
|
||||
}
|
||||
if (oredMSR & 0x100)
|
||||
{
|
||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR, "Run failure at %d", axisNo_);
|
||||
updateMsgTxtFromDriver("Run failure");
|
||||
comStatus = asynError;
|
||||
errorReported = 1;
|
||||
goto skip;
|
||||
}
|
||||
if (oredMSR & 0x400)
|
||||
{
|
||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR, "Positioning failure at %d", axisNo_);
|
||||
updateMsgTxtFromDriver("Positioning failure");
|
||||
comStatus = asynError;
|
||||
errorReported = 1;
|
||||
goto skip;
|
||||
}
|
||||
if (oredMSR & 0x200 || oredMSR & 0x80)
|
||||
{
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_WARNING, "Positioning fault at %d", axisNo_);
|
||||
}
|
||||
setIntegerParam(pC_->motorStatusProblem_, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
*moving = true;
|
||||
next_poll = -1;
|
||||
setIntegerParam(pC_->motorStatusDone_, false);
|
||||
//updateMsgTxtFromDriver("Creeping");
|
||||
}
|
||||
|
||||
skip:
|
||||
if(driverError == 0){
|
||||
updateMsgTxtFromDriver(NULL);
|
||||
}
|
||||
setIntegerParam(pC_->motorStatusProblem_, comStatus ? 1:0);
|
||||
skip:
|
||||
callParamCallbacks();
|
||||
return comStatus ? asynError : asynSuccess;
|
||||
return comStatus;
|
||||
}
|
||||
|
||||
/** Code for iocsh registration */
|
||||
static const iocshArg EL734CreateControllerArg0 = {"Port name", iocshArgString};
|
||||
static const iocshArg EL734CreateControllerArg1 = {"EL734 port name", iocshArgString};
|
||||
static const iocshArg EL734CreateControllerArg2 = {"Number of axes", iocshArgInt};
|
||||
static const iocshArg * const EL734CreateControllerArgs[] = {&EL734CreateControllerArg0,
|
||||
&EL734CreateControllerArg1,
|
||||
&EL734CreateControllerArg2};
|
||||
static const iocshArg *const EL734CreateControllerArgs[] = {&EL734CreateControllerArg0,
|
||||
&EL734CreateControllerArg1,
|
||||
&EL734CreateControllerArg2};
|
||||
static const iocshFuncDef EL734CreateControllerDef = {"EL734CreateController", 3, EL734CreateControllerArgs};
|
||||
static void EL734CreateContollerCallFunc(const iocshArgBuf *args)
|
||||
{
|
||||
@ -468,6 +606,7 @@ static void EL734Register(void)
|
||||
iocshRegister(&EL734CreateControllerDef, EL734CreateContollerCallFunc);
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
epicsExportRegistrar(EL734Register);
|
||||
extern "C"
|
||||
{
|
||||
epicsExportRegistrar(EL734Register);
|
||||
}
|
||||
|
@ -28,33 +28,32 @@ public:
|
||||
asynStatus setClosedLoop(bool closedLoop);
|
||||
|
||||
private:
|
||||
EL734Controller *pC_; /**< Pointer to the asynMotorController to which this axis belongs.
|
||||
* Abbreviated because it is used very frequently */
|
||||
EL734Controller *pC_; /**< Pointer to the asynMotorController to which this axis belongs.
|
||||
* Abbreviated because it is used very frequently */
|
||||
int oredMSR;
|
||||
double position;
|
||||
int homing;
|
||||
time_t next_poll;
|
||||
int ErrTxtIdx;
|
||||
int errorReported;
|
||||
|
||||
void forwardErrorTxt(EL734Axis *axis);
|
||||
|
||||
friend class EL734Controller;
|
||||
friend class EL734Controller;
|
||||
};
|
||||
|
||||
class EL734Controller : public SINQController {
|
||||
class EL734Controller : public SINQController
|
||||
{
|
||||
public:
|
||||
EL734Controller(const char *portName, const char *EL734PortName, int numAxes);
|
||||
|
||||
void report(FILE *fp, int level);
|
||||
EL734Axis* getAxis(asynUser *pasynUser);
|
||||
EL734Axis* getAxis(int axisNo);
|
||||
EL734Axis *getAxis(asynUser *pasynUser);
|
||||
EL734Axis *getAxis(int axisNo);
|
||||
|
||||
friend class EL734Axis;
|
||||
private:
|
||||
asynUser *pasynUserController_;
|
||||
friend class EL734Axis;
|
||||
|
||||
asynStatus transactController(int axis, char command[COMLEN], char reply[COMLEN]);
|
||||
private:
|
||||
asynUser *pasynUserController_;
|
||||
|
||||
void switchRemote();
|
||||
asynStatus transactController(int axis, char command[COMLEN], char reply[COMLEN]);
|
||||
|
||||
void switchRemote();
|
||||
};
|
||||
|
679
sinqEPICSApp/src/EuroMoveDriver.cpp
Normal file
679
sinqEPICSApp/src/EuroMoveDriver.cpp
Normal file
@ -0,0 +1,679 @@
|
||||
/*
|
||||
FILENAME... EuroMoveDriver.cpp
|
||||
USAGE... Motor driver support for the EuroMove motor controller from LLB.
|
||||
|
||||
This is a driver for the EuroMove motor controller from LLB. This device talks to us either via
|
||||
GPIB or USB. The controller is fairly simple. It can control quite a number of motors.
|
||||
|
||||
Reworked in 06/2021 to work with the Prologix GPIV-Ethernet converter
|
||||
|
||||
mark.koennecke@psi.ch
|
||||
*/
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <ctype.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <iocsh.h>
|
||||
#include <epicsThread.h>
|
||||
#include <errlog.h>
|
||||
|
||||
#include <asynOctetSyncIO.h>
|
||||
|
||||
#include "EuroMoveDriver.h"
|
||||
#include <epicsExport.h>
|
||||
|
||||
#define IDLEPOLL 60
|
||||
|
||||
/** Creates a new EuroMoveController object.
|
||||
* \param[in] portName The name of the asyn port that will be created for this driver
|
||||
* \param[in] EuroMovePortName The name of the drvAsynSerialPort that was created previously to connect to the EuroMove controller
|
||||
* \param[in] numAxes The number of axes that this controller supports
|
||||
*/
|
||||
EuroMoveController::EuroMoveController(const char *portName, const char *EuroMovePortName, int gpibAddr, int numAxis)
|
||||
: SINQController(portName, EuroMovePortName,numAxis+1)
|
||||
{
|
||||
asynStatus status;
|
||||
static const char *functionName = "EuroMoveController::EuroMoveController";
|
||||
size_t out, in;
|
||||
int reason;
|
||||
char command[80], reply[80];
|
||||
|
||||
/* Connect to EuroMove controller */
|
||||
status = pasynOctetSyncIO->connect(EuroMovePortName, 0, &pasynUserController_, NULL);
|
||||
if (status) {
|
||||
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"%s: cannot connect to EuroMove controller\n",
|
||||
functionName);
|
||||
}
|
||||
const char *terminator = "\r\n";
|
||||
pasynOctetSyncIO->setOutputEos(pasynUserController_,terminator,strlen(terminator));
|
||||
const char *repTerminator = "\r";
|
||||
pasynOctetSyncIO->setInputEos(pasynUserController_,repTerminator,strlen(repTerminator));
|
||||
|
||||
/* Save gpib address and prepare addr string */
|
||||
this->gpibAddr = gpibAddr;
|
||||
snprintf(this->addrCommand,sizeof(addrCommand), "++addr %d", gpibAddr);
|
||||
|
||||
/*
|
||||
Configure the Prologix interface to our liking
|
||||
The thing sends no response on configuration commands
|
||||
*/
|
||||
strcpy(command,"++mode 1"); /* Make it a controller */
|
||||
status = pasynOctetSyncIO->write(pasynUserController_, command, strlen(command), 2, &out);
|
||||
strcpy(command,"++auto 1"); /* ask for response automatically */
|
||||
status = pasynOctetSyncIO->write(pasynUserController_, command, strlen(command), 2, &out);
|
||||
strcpy(command,"++eos 1"); /* CR */
|
||||
status = pasynOctetSyncIO->write(pasynUserController_, command, strlen(command), 2, &out);
|
||||
|
||||
|
||||
|
||||
for(int i = 0; i < numAxis; i++){
|
||||
new EuroMoveAxis(this, i+1);
|
||||
}
|
||||
startPoller(1000./1000., IDLEPOLL, 2);
|
||||
}
|
||||
|
||||
|
||||
/** Creates a new EuroMoveController object.
|
||||
* Configuration command, called directly or from iocsh
|
||||
* \param[in] portName The name of the asyn port that will be created for this driver
|
||||
* \param[in] EuroMovePortName The name of the drvAsynIPPPort that was created previously to connect to the EuroMove controller
|
||||
* \param[in] gpibAddr The address of the controller on the GPIB bus
|
||||
* \param[in] numAxes The number of axes that this controller supports
|
||||
*/
|
||||
extern "C" int EuroMoveCreateController(const char *portName, const char *EuroMovePortName, const int gpibAddr, const int numAxis)
|
||||
{
|
||||
new EuroMoveController(portName, EuroMovePortName, gpibAddr, numAxis);
|
||||
return(asynSuccess);
|
||||
}
|
||||
|
||||
/** Reports on status of the driver
|
||||
* \param[in] fp The file pointer on which report information will be written
|
||||
* \param[in] level The level of report detail desired
|
||||
*
|
||||
* If details > 0 then information is printed about each axis.
|
||||
* After printing controller-specific information it calls asynMotorController::report()
|
||||
*/
|
||||
void EuroMoveController::report(FILE *fp, int level)
|
||||
{
|
||||
fprintf(fp, "EuroMove motor driver %s, numAxes=%d\n",
|
||||
this->portName, numAxes_);
|
||||
|
||||
// Call the base class method
|
||||
asynMotorController::report(fp, level);
|
||||
}
|
||||
|
||||
/** Returns a pointer to an EuroMoveAxis object.
|
||||
* Returns NULL if the axis number encoded in pasynUser is invalid.
|
||||
* \param[in] pasynUser asynUser structure that encodes the axis index number. */
|
||||
EuroMoveAxis* EuroMoveController::getAxis(asynUser *pasynUser)
|
||||
{
|
||||
return static_cast<EuroMoveAxis*>(asynMotorController::getAxis(pasynUser));
|
||||
}
|
||||
|
||||
/** Returns a pointer to an EuroMoveAxis object.
|
||||
* Returns NULL if the axis number encoded in pasynUser is invalid.
|
||||
* \param[in] axisNo Axis index number. */
|
||||
EuroMoveAxis* EuroMoveController::getAxis(int axisNo)
|
||||
{
|
||||
return static_cast<EuroMoveAxis*>(asynMotorController::getAxis(axisNo));
|
||||
}
|
||||
|
||||
/**
|
||||
* send a command to the EuroMove and read the reply. Do test for errors.
|
||||
* \param[in] command The command to send
|
||||
* \param[out] reply The controllers reply
|
||||
*/
|
||||
|
||||
asynStatus EuroMoveController::transactController(int axisNo,char command[COMLEN], char reply[COMLEN])
|
||||
{
|
||||
asynStatus status;
|
||||
SINQAxis *axis = getAxis(axisNo);
|
||||
size_t out, in;
|
||||
int reason, errCode, lCode;
|
||||
char pDummy[50], lReply[50];
|
||||
const char *lCommand = {"L"};
|
||||
|
||||
|
||||
|
||||
pasynOctetSyncIO->flush(pasynUserController_);
|
||||
|
||||
/* set address first */
|
||||
status = pasynOctetSyncIO->write(pasynUserController_, this->addrCommand, strlen(this->addrCommand), 2, &out);
|
||||
if(status != asynSuccess){
|
||||
if(axis!= NULL){
|
||||
axis->updateMsgTxtFromDriver("Lost connection to motor controller");
|
||||
errlogPrintf("Lost connection to motor controller\n");
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
/* now the actual command */
|
||||
status = pasynOctetSyncIO->writeRead(pasynUserController_, command, strlen(command),
|
||||
reply,sizeof(reply), 10.,&out,&in,&reason);
|
||||
if(status != asynSuccess){
|
||||
if(axis!= NULL){
|
||||
axis->updateMsgTxtFromDriver("Lost connection to motor controller");
|
||||
errlogPrintf("Lost connection to motor controller\n");
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
if(strstr(reply,"ERROR") != NULL){
|
||||
sscanf((const char *)reply, "%s %d", pDummy, &errCode);
|
||||
switch(errCode){
|
||||
case 0:
|
||||
axis->updateMsgTxtFromDriver("Syntax error or parameter out of range");
|
||||
errlogPrintf("Syntax error or parameter out or range on %d\n", axisNo);
|
||||
break;
|
||||
case 1:
|
||||
axis->updateMsgTxtFromDriver("Timeout communicating with daughter board");
|
||||
errlogPrintf("Timeout communicating with daughter board on %d\n", axisNo);
|
||||
break;
|
||||
case 2:
|
||||
axis->updateMsgTxtFromDriver("This is not a drivable motor");
|
||||
errlogPrintf("This is not a drivable motor on %d\n", axisNo);
|
||||
break;
|
||||
case 3:
|
||||
axis->updateMsgTxtFromDriver("Encoder anomaly");
|
||||
errlogPrintf("Encoder anomaly on %d\n", axisNo);
|
||||
break;
|
||||
case 4:
|
||||
axis->updateMsgTxtFromDriver("Attempt to move across limit switch");
|
||||
errlogPrintf("Attempt to move across limit switch on %d\n", axisNo);
|
||||
break;
|
||||
case 5:
|
||||
axis->updateMsgTxtFromDriver("Badly configured relay system");
|
||||
errlogPrintf("Badly configured relay system on %d\n", axisNo);
|
||||
break;
|
||||
case 6:
|
||||
axis->updateMsgTxtFromDriver("Non valid backup");
|
||||
errlogPrintf("Invalid backup on %d\n", axisNo);
|
||||
break;
|
||||
case 7:
|
||||
axis->updateMsgTxtFromDriver("Backup failed");
|
||||
errlogPrintf("Backup failed on %d\n", axisNo);
|
||||
break;
|
||||
}
|
||||
return asynError;
|
||||
}
|
||||
|
||||
/*
|
||||
Test system status
|
||||
*/
|
||||
status = pasynOctetSyncIO->writeRead(pasynUserController_, lCommand, strlen(lCommand),
|
||||
lReply,sizeof(lReply), 1.,&out,&in,&reason);
|
||||
if(status != asynSuccess){
|
||||
if(axis!= NULL){
|
||||
axis->updateMsgTxtFromDriver("Lost connection to motor controller");
|
||||
errlogPrintf("Lost connection to motor controller\n");
|
||||
}
|
||||
return status;
|
||||
}
|
||||
sscanf(lReply, "%x", &lCode);
|
||||
//errlogPrintf("System status returned %s, converted to %d\n", lReply, lCode);
|
||||
if((lCode & 1) > 0){
|
||||
axis->updateMsgTxtFromDriver("Syntax error or impossible to execute");
|
||||
errlogPrintf("Syntax error or impossible to execute for %s on %d\n", command, axisNo);
|
||||
return asynError;
|
||||
}
|
||||
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
asynStatus EuroMoveController::writeFloat64(asynUser *pasynUser, epicsFloat64 value)
|
||||
{
|
||||
int function = pasynUser->reason;
|
||||
asynStatus status = asynError;
|
||||
EuroMoveAxis *pAxis = NULL;
|
||||
|
||||
static const char *functionName = "EuroMoveController::writeFloat64";
|
||||
|
||||
|
||||
pAxis = (EuroMoveAxis *)this->getAxis(pasynUser);
|
||||
if (!pAxis) {
|
||||
return asynError;
|
||||
}
|
||||
|
||||
|
||||
/* Set the parameter and readback in the parameter library. */
|
||||
status = pAxis->setDoubleParam(function, value);
|
||||
|
||||
errlogPrintf("%s, reason %d, value %f\n", functionName, function, value);
|
||||
|
||||
// TODO: somethign is really shitty here: lowLimit and highLimit cannot be on the command
|
||||
if (function == motorResolution_) {
|
||||
if(value > .0){
|
||||
pAxis->resolution = (int)(1./value);
|
||||
} else {
|
||||
pAxis->resolution = 1;
|
||||
}
|
||||
}
|
||||
|
||||
//Call base class method
|
||||
//This will handle callCallbacks even if the function was handled here.
|
||||
status = asynMotorController::writeFloat64(pasynUser, value);
|
||||
|
||||
return status;
|
||||
|
||||
}
|
||||
|
||||
// These are the EuroMoveAxis methods
|
||||
|
||||
/** Creates a new EuroMoveAxis object.
|
||||
* \param[in] pC Pointer to the EuroMoveController to which this axis belongs.
|
||||
* \param[in] axisNo Index number of this axis, range 0 to pC->numAxes_-1.
|
||||
*
|
||||
* Initializes register numbers, etc.
|
||||
*/
|
||||
EuroMoveAxis::EuroMoveAxis(EuroMoveController *pC, int axisNo)
|
||||
: SINQAxis(pC, axisNo),
|
||||
pC_(pC)
|
||||
{
|
||||
motNo = axisNo;
|
||||
next_poll = -1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** Reports on status of the axis
|
||||
* \param[in] fp The file pointer on which report information will be written
|
||||
* \param[in] level The level of report detail desired
|
||||
*
|
||||
* After printing device-specific information calls asynMotorAxis::report()
|
||||
*/
|
||||
void EuroMoveAxis::report(FILE *fp, int level)
|
||||
{
|
||||
if (level > 0) {
|
||||
fprintf(fp, " axis %d\n",
|
||||
axisNo_);
|
||||
}
|
||||
asynMotorAxis::report(fp, level);
|
||||
}
|
||||
|
||||
|
||||
asynStatus EuroMoveAxis::move(double position, int relative, double minVelocity, double maxVelocity, double acceleration)
|
||||
{
|
||||
asynStatus status;
|
||||
//static const char *functionName = "EuroMoveAxis::move";
|
||||
char command[COMLEN], reply[COMLEN];
|
||||
|
||||
updateMsgTxtFromDriver("");
|
||||
|
||||
if (relative) {
|
||||
position += this->position;
|
||||
}
|
||||
homingStatus = HomeIdle;
|
||||
sprintf(command,"G%d=%d", motNo, (int)position);
|
||||
status = pC_->transactController(motNo,command,reply);
|
||||
next_poll = -1;
|
||||
targetPosition = (int)position;
|
||||
return status;
|
||||
}
|
||||
|
||||
asynStatus EuroMoveAxis::home(double minVelocity, double maxVelocity, double acceleration, int forwards)
|
||||
{
|
||||
asynStatus status = asynSuccess;
|
||||
//static const char *functionName = "EuroMoveAxis::home";
|
||||
char command[COMLEN], reply[COMLEN];
|
||||
|
||||
/*
|
||||
The EuroMove controller does not have a built in homing mechanism. We simulate it by:
|
||||
|
||||
1). Running the motor in the appropriate end swicth at high speed
|
||||
2) Stepping back three units
|
||||
3) Run the motor again in the end switch at low speed for better accuracy
|
||||
4) Set the limit position with the <I> command
|
||||
|
||||
The state of this operation is maintained in the homingStatus variable
|
||||
*/
|
||||
homingDirection = forwards;
|
||||
homingStatus = HomeFastRun;
|
||||
if(homingDirection){
|
||||
sprintf(command,"H%d=03", motNo);
|
||||
} else {
|
||||
sprintf(command,"H%d=07", motNo);
|
||||
}
|
||||
|
||||
status = pC_->transactController(motNo,command,reply);
|
||||
next_poll = -1;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
asynStatus EuroMoveAxis::moveVelocity(double minVelocity, double maxVelocity, double acceleration)
|
||||
{
|
||||
asynStatus status;
|
||||
double target;
|
||||
|
||||
//static const char *functionName = "EuroMoveAxis::moveVelocity";
|
||||
|
||||
// asynPrint(pasynUser_, ASYN_TRACE_FLOW,
|
||||
// "%s: minVelocity=%f, maxVelocity=%f, acceleration=%f\n",
|
||||
// functionName, minVelocity, maxVelocity, acceleration);
|
||||
|
||||
updateMsgTxtFromDriver("");
|
||||
|
||||
if (maxVelocity > 0.) {
|
||||
/* This is a positive move */
|
||||
pC_->getDoubleParam(axisNo_,pC_->motorHighLimit_,&target);
|
||||
} else {
|
||||
/* This is a negative move */
|
||||
pC_->getDoubleParam(axisNo_,pC_->motorLowLimit_,&target);
|
||||
}
|
||||
status = move(target,0,0,0,0);
|
||||
next_poll = -1;
|
||||
return status;
|
||||
}
|
||||
|
||||
asynStatus EuroMoveAxis::sendStop()
|
||||
{
|
||||
asynStatus status;
|
||||
char command[COMLEN], reply[COMLEN];
|
||||
|
||||
sprintf(command, "B%d", motNo);
|
||||
status = pC_->transactController(axisNo_,command,reply);
|
||||
return status;
|
||||
}
|
||||
|
||||
asynStatus EuroMoveAxis::stop(double acceleration )
|
||||
{
|
||||
asynStatus status;
|
||||
// static const char *functionName = "EuroMoveAxis::stop";
|
||||
|
||||
status = sendStop();
|
||||
errlogPrintf("Sent STOP on Axis %d\n", axisNo_);
|
||||
updateMsgTxtFromDriver("Axis interrupted");
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
asynStatus EuroMoveAxis::setPosition(double position)
|
||||
{
|
||||
asynStatus status;
|
||||
//static const char *functionName = "EuroMoveAxis::setPosition";
|
||||
char command[COMLEN], reply[COMLEN];
|
||||
|
||||
errlogPrintf("EuroMoveAxis::setPosition called with %lf\n", position);
|
||||
|
||||
sprintf(command, "I%d=%d",motNo,(int)position);
|
||||
status = pC_->transactController(axisNo_,command,reply);
|
||||
next_poll = -1;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
asynStatus EuroMoveAxis::setClosedLoop(bool closedLoop)
|
||||
{
|
||||
//static const char *functionName = "EuroMoveAxis::setClosedLoop";
|
||||
|
||||
/*
|
||||
This belongs into the Kingdom of Electronics.
|
||||
We do not do this.
|
||||
*/
|
||||
|
||||
return asynError;
|
||||
}
|
||||
|
||||
/** Polls the axis.
|
||||
* This function reads the motor position, the limit status, the home status, the moving status,
|
||||
* and the drive power-on status.
|
||||
* It calls setIntegerParam() and setDoubleParam() for each item that it polls,
|
||||
* and then calls callParamCallbacks() at the end.
|
||||
* \param[out] moving A flag that is set indicating that the axis is moving (true) or done (false). */
|
||||
asynStatus EuroMoveAxis::poll(bool *moving)
|
||||
{
|
||||
asynStatus comStatus = asynSuccess;
|
||||
char command[COMLEN], reply[COMLEN];
|
||||
unsigned int axStatus;
|
||||
double backStep, backPos, limit;
|
||||
|
||||
// protect against excessive polling
|
||||
if(time(NULL) < next_poll){
|
||||
*moving = false;
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
setIntegerParam(pC_->motorStatusProblem_, false);
|
||||
|
||||
/*
|
||||
read the current position
|
||||
*/
|
||||
sprintf(command,"A%d",motNo);
|
||||
comStatus = pC_->transactController(axisNo_,command,reply);
|
||||
if(comStatus == asynError) {
|
||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
goto skip;
|
||||
}
|
||||
sscanf(reply,"%d",&position);
|
||||
setDoubleParam(pC_->motorPosition_,(double)position);
|
||||
|
||||
// Read the moving status of this motor
|
||||
sprintf(command,"E%d",motNo);
|
||||
comStatus = pC_->transactController(axisNo_,command,reply);
|
||||
if(comStatus == asynError){
|
||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
goto skip;
|
||||
}
|
||||
|
||||
/* errlogPrintf("Axis %d, status reply %s, position %d\n", axisNo_, reply, position); */
|
||||
sscanf(reply, "%x", &axStatus);
|
||||
|
||||
if(homingStatus == HomeIdle){
|
||||
if((axStatus & 128) > 0) { // test bit 8
|
||||
*moving = true;
|
||||
next_poll = -1;
|
||||
setIntegerParam(pC_->motorStatusDone_, false);
|
||||
} else {
|
||||
*moving = false;
|
||||
next_poll = time(NULL)+IDLEPOLL;
|
||||
setIntegerParam(pC_->motorStatusDone_, true);
|
||||
}
|
||||
|
||||
/* Testing limit switches */
|
||||
if((axStatus & 2) > 0){ // bit 2
|
||||
if(targetPosition > position) {
|
||||
/*
|
||||
Error codition only when we wish to move further
|
||||
*/
|
||||
setIntegerParam(pC_->motorStatusHighLimit_, true);
|
||||
errlogPrintf("HighLimit detected on %d\n", motNo);
|
||||
updateMsgTxtFromDriver("On high limit switch");
|
||||
sendStop();
|
||||
}
|
||||
} else {
|
||||
setIntegerParam(pC_->motorStatusHighLimit_, false);
|
||||
}
|
||||
if((axStatus & 1) > 0){ // bit 1
|
||||
setIntegerParam(pC_->motorStatusLowLimit_, true);
|
||||
if(targetPosition < position) {
|
||||
/*
|
||||
Error treatment onlly when we want to go below the limits
|
||||
*/
|
||||
errlogPrintf("LowLimit detected on %d\n", motNo);
|
||||
sendStop();
|
||||
updateMsgTxtFromDriver("On low limit switch");
|
||||
}
|
||||
} else {
|
||||
setIntegerParam(pC_->motorStatusLowLimit_, false);
|
||||
}
|
||||
|
||||
/* there could be errors reported in the motor status */
|
||||
if((axStatus & 8) > 0){ // bit 4
|
||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
updateMsgTxtFromDriver("Internal timeout detected");
|
||||
errlogPrintf("Internal timeout detected on %d\n", motNo);
|
||||
comStatus = asynError;
|
||||
}
|
||||
if((axStatus & 4) > 0){ // bit 3
|
||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
updateMsgTxtFromDriver("Encoding anomaly");
|
||||
errlogPrintf("Encoding anomaly on %d\n", motNo);
|
||||
comStatus = asynError;
|
||||
}
|
||||
} else {
|
||||
next_poll = -1;
|
||||
setIntegerParam(pC_->motorStatusDone_, false);
|
||||
*moving = true;
|
||||
switch(homingStatus){
|
||||
case HomeIdle:
|
||||
// handled above: this is here to silence the compiler
|
||||
break;
|
||||
case HomeFastRun:
|
||||
// errlogPrintf("HomeFastRun\n");
|
||||
if(axStatus & 2 || axStatus & 1){
|
||||
sendStop();
|
||||
homingStatus = HomeFastStop;
|
||||
}
|
||||
break;
|
||||
case HomeFastStop:
|
||||
// errlogPrintf("HomeFastStop\n");
|
||||
if((axStatus & 128) == 0){
|
||||
backStep = 3 * resolution;
|
||||
if(homingDirection){
|
||||
backPos = position - backStep;
|
||||
} else {
|
||||
backPos = position + backStep;
|
||||
}
|
||||
sprintf(command,"G%d=%d", motNo, (int)backPos);
|
||||
comStatus = pC_->transactController(motNo,command,reply);
|
||||
if(comStatus == asynError){
|
||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
updateMsgTxtFromDriver("Homing problem when back stepping");
|
||||
errlogPrintf("Homing problem when back stepping: %s not accepted\n", command);
|
||||
*moving = false;
|
||||
setIntegerParam(pC_->motorStatusAtHome_, true);
|
||||
homingStatus = HomeIdle;
|
||||
setIntegerParam(pC_->motorStatusDone_, true);
|
||||
goto skip;
|
||||
}
|
||||
usleep(200);
|
||||
homingStatus = HomeBackStep;
|
||||
}
|
||||
break;
|
||||
case HomeBackStep:
|
||||
//errlogPrintf("HomeBackStep\n");
|
||||
if((axStatus & 128) == 0){
|
||||
homingStatus = HomeSlowRun;
|
||||
if(homingDirection){
|
||||
sprintf(command,"H%d=01", motNo);
|
||||
} else {
|
||||
sprintf(command,"H%d=05", motNo);
|
||||
}
|
||||
comStatus = pC_->transactController(motNo,command,reply);
|
||||
if(comStatus == asynError){
|
||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
updateMsgTxtFromDriver("Homing problem slow running");
|
||||
errlogPrintf("Homing problem when slow running: %s not accepted\n", command);
|
||||
*moving = false;
|
||||
setIntegerParam(pC_->motorStatusAtHome_, true);
|
||||
homingStatus = HomeIdle;
|
||||
setIntegerParam(pC_->motorStatusDone_, true);
|
||||
goto skip;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case HomeSlowRun:
|
||||
//errlogPrintf("HomeSlowRun\n");
|
||||
if(axStatus & 2 || axStatus & 1){
|
||||
sendStop();
|
||||
homingStatus = HomeSlowStop;
|
||||
}
|
||||
break;
|
||||
case HomeSlowStop:
|
||||
//errlogPrintf("HomeSlowStop\n");
|
||||
if((axStatus & 128) == 0) {
|
||||
if(homingDirection) {
|
||||
pC_->getDoubleParam(axisNo_,pC_->motorHighLimit_,&limit);
|
||||
} else {
|
||||
pC_->getDoubleParam(axisNo_,pC_->motorLowLimit_,&limit);
|
||||
}
|
||||
setPosition(limit);
|
||||
*moving = false;
|
||||
setIntegerParam(pC_->motorStatusDone_, true);
|
||||
next_poll = time(NULL)+IDLEPOLL;
|
||||
setIntegerParam(pC_->motorStatusDone_, true);
|
||||
setIntegerParam(pC_->motorStatusAtHome_, true);
|
||||
homingStatus = HomeIdle;
|
||||
setDoubleParam(pC_->motorPosition_,(double)limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
skip:
|
||||
callParamCallbacks();
|
||||
return comStatus;
|
||||
}
|
||||
|
||||
/** Code for configuring the motNo on axis **/
|
||||
extern "C" {
|
||||
asynStatus EuroMoveConfigureAxis(const char *euroMoveName, unsigned int axisNo, unsigned int motNo)
|
||||
{
|
||||
EuroMoveController *pC = NULL;
|
||||
EuroMoveAxis *pAxis = NULL;
|
||||
|
||||
pC = (EuroMoveController *)findAsynPortDriver(euroMoveName);
|
||||
if(!pC){
|
||||
errlogPrintf("%s driver not found", euroMoveName);
|
||||
return asynError;
|
||||
}
|
||||
pC->unlock();
|
||||
|
||||
pAxis = (EuroMoveAxis *)pC->getAxis(axisNo);
|
||||
if(!pAxis){
|
||||
errlogPrintf("axis %d driver not found on %s", axisNo, euroMoveName);
|
||||
return asynError;
|
||||
}
|
||||
pAxis->setMotNo(motNo);
|
||||
|
||||
return asynSuccess;
|
||||
}
|
||||
}
|
||||
|
||||
static const iocshArg EuroMoveConfigArg0 = {"Port name", iocshArgString};
|
||||
static const iocshArg EuroMoveConfigArg1 = {"Axis Index", iocshArgInt};
|
||||
static const iocshArg EuroMoveConfigArg2 = {"Motor number in EuroMove", iocshArgInt};
|
||||
static const iocshArg * const EuroMoveConfigArgs[] = {&EuroMoveConfigArg0,
|
||||
&EuroMoveConfigArg1,
|
||||
&EuroMoveConfigArg2
|
||||
};
|
||||
static const iocshFuncDef EuroMoveConfigDef = {"EuroMoveConfigureAxis", 3, EuroMoveConfigArgs};
|
||||
static void EuroMoveConfigCallFunc(const iocshArgBuf *args)
|
||||
{
|
||||
EuroMoveConfigureAxis(args[0].sval, (unsigned int)args[1].ival, (unsigned int)args[2].ival);
|
||||
}
|
||||
|
||||
|
||||
/** Code for iocsh registration */
|
||||
static const iocshArg EuroMoveCreateControllerArg0 = {"Port name", iocshArgString};
|
||||
static const iocshArg EuroMoveCreateControllerArg1 = {"EuroMove port name", iocshArgString};
|
||||
static const iocshArg EuroMoveCreateControllerArg2 = {"GPIB Address", iocshArgInt};
|
||||
static const iocshArg EuroMoveCreateControllerArg3 = {"Number of axis", iocshArgInt};
|
||||
static const iocshArg * const EuroMoveCreateControllerArgs[] = {&EuroMoveCreateControllerArg0,
|
||||
&EuroMoveCreateControllerArg1,
|
||||
&EuroMoveCreateControllerArg2,
|
||||
&EuroMoveCreateControllerArg3
|
||||
};
|
||||
static const iocshFuncDef EuroMoveCreateControllerDef = {"EuroMoveCreateController", 4, EuroMoveCreateControllerArgs};
|
||||
static void EuroMoveCreateControllerCallFunc(const iocshArgBuf *args)
|
||||
{
|
||||
EuroMoveCreateController(args[0].sval, args[1].sval, args[2].ival, args[3].ival);
|
||||
}
|
||||
|
||||
static void EuroMoveRegister(void)
|
||||
{
|
||||
iocshRegister(&EuroMoveCreateControllerDef, EuroMoveCreateControllerCallFunc);
|
||||
iocshRegister(&EuroMoveConfigDef, EuroMoveConfigCallFunc);
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
epicsExportRegistrar(EuroMoveRegister);
|
||||
}
|
72
sinqEPICSApp/src/EuroMoveDriver.h
Normal file
72
sinqEPICSApp/src/EuroMoveDriver.h
Normal file
@ -0,0 +1,72 @@
|
||||
/*
|
||||
FILENAME... EuroMoveDriver.h
|
||||
USAGE... Motor driver support for the LLB EuroMove controller
|
||||
|
||||
Mark Koennecke
|
||||
January 2020
|
||||
|
||||
*/
|
||||
|
||||
#include "SINQController.h"
|
||||
#include "SINQAxis.h"
|
||||
|
||||
#define COMLEN 80
|
||||
|
||||
typedef enum {
|
||||
HomeIdle,
|
||||
HomeFastRun,
|
||||
HomeFastStop,
|
||||
HomeBackStep,
|
||||
HomeSlowRun,
|
||||
HomeSlowStop,
|
||||
} HomingStatus;
|
||||
|
||||
class EuroMoveAxis : public SINQAxis
|
||||
{
|
||||
public:
|
||||
/* These are the methods we override from the base class */
|
||||
EuroMoveAxis(class EuroMoveController *pC, int axis);
|
||||
void report(FILE *fp, int level);
|
||||
asynStatus move(double position, int relative, double min_velocity, double max_velocity, double acceleration);
|
||||
asynStatus moveVelocity(double min_velocity, double max_velocity, double acceleration);
|
||||
asynStatus home(double min_velocity, double max_velocity, double acceleration, int forwards);
|
||||
asynStatus stop(double acceleration);
|
||||
asynStatus poll(bool *moving);
|
||||
asynStatus setPosition(double position);
|
||||
asynStatus setClosedLoop(bool closedLoop);
|
||||
void setMotNo(unsigned int no){
|
||||
motNo = no;
|
||||
}
|
||||
|
||||
private:
|
||||
EuroMoveController *pC_; /**< Pointer to the asynMotorController to which this axis belongs.
|
||||
* Abbreviated because it is used very frequently */
|
||||
time_t next_poll;
|
||||
int motNo; // motor number in the EuroMove controller
|
||||
int position;
|
||||
HomingStatus homingStatus;
|
||||
int homingDirection; // 1 = forward, 0 backwards
|
||||
asynStatus sendStop();
|
||||
int resolution;
|
||||
friend class EuroMoveController;
|
||||
int targetPosition;
|
||||
};
|
||||
|
||||
class EuroMoveController : public SINQController {
|
||||
public:
|
||||
EuroMoveController(const char *portName, const char *EuroMovePortName, const int gpibAddr, const int nAxis);
|
||||
|
||||
void report(FILE *fp, int level);
|
||||
EuroMoveAxis* getAxis(asynUser *pasynUser);
|
||||
EuroMoveAxis* getAxis(int axisNo);
|
||||
asynStatus writeFloat64(asynUser *pasynUser, epicsFloat64 value);
|
||||
|
||||
friend class EuroMoveAxis;
|
||||
private:
|
||||
asynUser *pasynUserController_;
|
||||
unsigned int gpibAddr;
|
||||
char addrCommand[50];
|
||||
|
||||
asynStatus transactController(int axisNo, char command[COMLEN], char reply[COMLEN]);
|
||||
|
||||
};
|
@ -28,6 +28,7 @@ sinqEPICS_SRCS += EL734Driver.cpp devScalerEL737.c pmacAsynIPPort.c SINQAxis.cpp
|
||||
sinqEPICS_SRCS += pmacController.cpp pmacAxis.cpp
|
||||
sinqEPICS_SRCS += NanotecDriver.cpp stptok.cpp
|
||||
sinqEPICS_SRCS += PhytronDriver.cpp
|
||||
sinqEPICS_SRCS += EuroMoveDriver.cpp
|
||||
|
||||
|
||||
# Build the main IOC entry point on workstation OSs.
|
||||
|
954
sinqEPICSApp/src/MasterMACSDriver.cpp
Normal file
954
sinqEPICSApp/src/MasterMACSDriver.cpp
Normal file
@ -0,0 +1,954 @@
|
||||
/*
|
||||
Driver for the MasterMACS motor controller used at SINQ
|
||||
For documentation see the standard SINQ place for hardware documentation or
|
||||
Marcel Schildt
|
||||
|
||||
Mark Koennecke, March-August 2023
|
||||
Mark Koenencke, More fixes in June 2024
|
||||
*/
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <ctype.h>
|
||||
#include <time.h>
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <iocsh.h>
|
||||
#include <epicsThread.h>
|
||||
#include <errlog.h>
|
||||
|
||||
#include <asynOctetSyncIO.h>
|
||||
|
||||
#include "MasterMACSDriver.h"
|
||||
#include <epicsExport.h>
|
||||
|
||||
#define CHECK_BIT(var,pos) ((var) & (1 << pos))
|
||||
#define ABS(x) (x < 0 ? -(x) : (x))
|
||||
|
||||
#define debug 0
|
||||
#define timeDebug 0
|
||||
|
||||
double DoubleTime(void)
|
||||
{
|
||||
struct timeval now;
|
||||
/* the resolution of this function is usec, if the machine supports this
|
||||
and the mantissa of a double is 51 bits or more (31 bits for seconds
|
||||
and 20 for microseconds)
|
||||
*/
|
||||
gettimeofday(&now, NULL);
|
||||
return now.tv_sec + now.tv_usec / 1e6;
|
||||
}
|
||||
|
||||
/** Creates a new MasterMACSController object.
|
||||
* \param[in] portName The name of the asyn port that will be created for this driver
|
||||
* \param[in] MasterMACSPortName The name of the drvAsynSerialPort that was created previously to connect to the MasterMACS controller
|
||||
* \param[in] numAxes The number of axes that this controller supports
|
||||
*/
|
||||
MasterMACSController::MasterMACSController(const char *portName,
|
||||
const char *MasterMACSPortName,
|
||||
int numAxes,
|
||||
int idlePoll, int busyPoll):SINQController
|
||||
(portName, MasterMACSPortName, numAxes)
|
||||
{
|
||||
asynStatus status;
|
||||
static const char *functionName =
|
||||
"MasterMACSController::MasterMACSController";
|
||||
char terminator[2] = "\x03";
|
||||
|
||||
|
||||
/* Connect to MasterMACS controller */
|
||||
status =
|
||||
pasynOctetSyncIO->connect(MasterMACSPortName, 0,
|
||||
&pasynUserController_, NULL);
|
||||
if (status) {
|
||||
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"%s: cannot connect to MasterMACS controller\n",
|
||||
functionName);
|
||||
}
|
||||
pasynOctetSyncIO->setOutputEos(pasynUserController_, terminator,
|
||||
strlen(terminator));
|
||||
pasynOctetSyncIO->setInputEos(pasynUserController_, terminator,
|
||||
strlen(terminator));
|
||||
|
||||
|
||||
pAxes_ = (MasterMACSAxis **) (asynMotorController::pAxes_);
|
||||
createParam(EnableAxisString, asynParamInt32, &enableAxis_);
|
||||
createParam(AxisEnabledString, asynParamInt32, &axisEnabled_);
|
||||
callParamCallbacks();
|
||||
|
||||
startPoller(busyPoll/1000., idlePoll/1000., 1);
|
||||
setIdlePollPeriod(idlePoll/1000.);
|
||||
setMovingPollPeriod(busyPoll/1000.);
|
||||
}
|
||||
|
||||
|
||||
/** Creates a new MasterMACSController object.
|
||||
* Configuration command, called directly or from iocsh
|
||||
* \param[in] portName The name of the asyn port that will be created for this driver
|
||||
* \param[in] MasterMACSPortName The name of the drvAsynIPPPort that was created previously to connect to the MasterMACS controller
|
||||
* \param[in] numAxes The number of axes that this controller supports
|
||||
* \param[in] idlePoll Poll interval when idle in microseconds
|
||||
* \param[in] busyPoll Poll interval when moving in microSeconds
|
||||
*/
|
||||
extern "C" int
|
||||
MasterMACSCreateController(const char *portName,
|
||||
const char *MasterMACSPortName, int numAxes, int idlePoll, int busyPoll)
|
||||
{
|
||||
MasterMACSController *pMasterMACSController
|
||||
= new MasterMACSController(portName, MasterMACSPortName, numAxes, idlePoll, busyPoll);
|
||||
pMasterMACSController = NULL;
|
||||
return (asynSuccess);
|
||||
}
|
||||
|
||||
/** Reports on status of the driver
|
||||
* \param[in] fp The file pointer on which report information will be written
|
||||
* \param[in] level The level of report detail desired
|
||||
*
|
||||
* If details > 0 then information is printed about each axis.
|
||||
* After printing controller-specific information it calls asynMotorController::report()
|
||||
*/
|
||||
void
|
||||
MasterMACSController::report(FILE * fp, int level)
|
||||
{
|
||||
fprintf(fp, "MasterMACS motor driver %s, numAxes=%d\n",
|
||||
this->portName, numAxes_);
|
||||
|
||||
// Call the base class method
|
||||
asynMotorController::report(fp, level);
|
||||
}
|
||||
|
||||
/** Returns a pointer to an MasterMACSAxis object.
|
||||
* Returns NULL if the axis number encoded in pasynUser is invalid.
|
||||
* \param[in] pasynUser asynUser structure that encodes the axis index number. */
|
||||
MasterMACSAxis *MasterMACSController::getAxis(asynUser * pasynUser)
|
||||
{
|
||||
return static_cast <
|
||||
MasterMACSAxis * >(asynMotorController::getAxis(pasynUser));
|
||||
}
|
||||
|
||||
/** Returns a pointer to an MasterMACSAxis object.
|
||||
* Returns NULL if the axis number encoded in pasynUser is invalid.
|
||||
* \param[in] axisNo Axis index number. */
|
||||
MasterMACSAxis *MasterMACSController::getAxis(int axisNo)
|
||||
{
|
||||
return static_cast <
|
||||
MasterMACSAxis * >(asynMotorController::getAxis(axisNo));
|
||||
}
|
||||
|
||||
/**
|
||||
* send a command to the MasterMACS and read the reply. Do some error and controller
|
||||
* issue fixing on the way
|
||||
* \param[in] command The command to send
|
||||
* \param[out] reply The controllers reply
|
||||
*/
|
||||
|
||||
asynStatus
|
||||
MasterMACSController::transactController(int axisNo,
|
||||
char command[COMLEN],
|
||||
char reply[COMLEN])
|
||||
{
|
||||
asynStatus status;
|
||||
size_t in, out;
|
||||
int reason, len, idx, lenPayload;
|
||||
unsigned int i;
|
||||
double startTime, now;
|
||||
char *mmacsData =
|
||||
NULL, ackchar, mmacsResponse[COMLEN], hexResponse[256];
|
||||
SINQAxis *axis = getAxis(axisNo);
|
||||
|
||||
pasynOctetSyncIO->flush(pasynUserController_);
|
||||
|
||||
/* read with a short timeout in order to remove duplicate messages
|
||||
* from the line. This also serves to slow down communication
|
||||
*/
|
||||
pasynOctetSyncIO->read(pasynUserController_, mmacsResponse, 35, interMessageSleep/1000., &in, &reason);
|
||||
|
||||
/* pack data for MasterMACS */
|
||||
len = strlen(command) + 6;
|
||||
mmacsData = (char *) malloc(len * sizeof(char));
|
||||
if (!mmacsData) {
|
||||
errlogSevPrintf(errlogMajor,
|
||||
"Failed to allocate memory in MasterMACSController::transactController");
|
||||
return asynError;
|
||||
}
|
||||
mmacsData[0] = 0x05;
|
||||
mmacsData[1] = (char) (len - 2);
|
||||
mmacsData[2] = 0;
|
||||
mmacsData[3] = 0x19;
|
||||
memcpy(mmacsData + 4, command, strlen(command) * sizeof(char));
|
||||
mmacsData[len - 2] = 0x0D;
|
||||
/* 0x03 is appended by asyn */
|
||||
|
||||
/* send the stuff away ... */
|
||||
if(debug) {
|
||||
errlogSevPrintf(errlogMajor,"Sending command: %s\n", command);
|
||||
}
|
||||
|
||||
startTime = DoubleTime();
|
||||
status =
|
||||
pasynOctetSyncIO->writeRead(pasynUserController_, mmacsData,
|
||||
len - 1, mmacsResponse, 35, 20., &out,
|
||||
&in, &reason);
|
||||
|
||||
if(timeDebug) {
|
||||
now = DoubleTime();
|
||||
if((now - startTime) > 1.) {
|
||||
errlogSevPrintf(errlogMajor, "Unusual long response time %lf to command %s\n", (now - startTime), command);
|
||||
}
|
||||
}
|
||||
|
||||
if (status != asynSuccess) {
|
||||
if (axis != NULL) {
|
||||
errlogSevPrintf(errlogMajor,
|
||||
"Lost connection to motor controller,a axis %d, reason %d",
|
||||
axisNo, reason);
|
||||
axis->updateMsgTxtFromDriver
|
||||
("Lost connection to motor controller");
|
||||
return status;
|
||||
}
|
||||
errlogSevPrintf(errlogMajor,
|
||||
"Lost connection to motor controller without axis, reason %d",
|
||||
reason);
|
||||
return status;
|
||||
}
|
||||
free(mmacsData);
|
||||
|
||||
/* format and print the response in hex for debugging purposes */
|
||||
if(debug) {
|
||||
for(i = 0, idx = 0; i < in; i++){
|
||||
sprintf(hexResponse + idx, "%02x ", (unsigned int)mmacsResponse[i]);
|
||||
idx = strlen(hexResponse);
|
||||
}
|
||||
errlogSevPrintf(errlogMajor,"Reply in hex: %s\n", hexResponse);
|
||||
}
|
||||
|
||||
/* Here we have read the data from the MasterMACS. We proceed to extract
|
||||
* the payload and the state of the ACK byte and place that reply into data
|
||||
*/
|
||||
if ((in < 33)) {
|
||||
errlogSevPrintf(errlogMajor,
|
||||
"MasterMACS only sent %d bytes, 34 expected",
|
||||
(int) in);
|
||||
return asynError;
|
||||
}
|
||||
/*
|
||||
* Unpack the MasterMACS message, start by finding <CR>
|
||||
*/
|
||||
for (i = 4, idx = 0; i < 34; i++) {
|
||||
if (mmacsResponse[i] == 0x0d) {
|
||||
idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
//errlogSevPrintf(errlogMajor, "Found <CR> at %d", idx);
|
||||
|
||||
/* one character before <CR> is the ACK, if it is there */
|
||||
ackchar = mmacsResponse[idx - 1];
|
||||
if (ackchar == 0x06) {
|
||||
strcpy(reply, "ACK:");
|
||||
} else if (ackchar == 0x15) {
|
||||
strcpy(reply, "NAK:");
|
||||
errlogSevPrintf(errlogMajor,
|
||||
"MasterMACS responded with NAK to %s\n", command);
|
||||
status = asynError;
|
||||
} else {
|
||||
/* the MasterMACS does not always send a ACK/NAK on read */
|
||||
strcpy(reply, "NON:");
|
||||
}
|
||||
//errlogSevPrintf(errlogMajor, "Reply after testing ACK byte: %s ", reply);
|
||||
|
||||
/* copy data */
|
||||
lenPayload = idx - 4;
|
||||
memcpy(reply + 4, mmacsResponse + 4, lenPayload);
|
||||
reply[4 + lenPayload] = (char) 0;
|
||||
if(debug) {
|
||||
errlogSevPrintf(errlogMajor, "Completed reply at end of transactController: %s ", reply);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------------------------------------*/
|
||||
asynStatus
|
||||
MasterMACSController::readInt32(asynUser * pasynUser,
|
||||
epicsInt32 * value)
|
||||
{
|
||||
|
||||
int function = pasynUser->reason;
|
||||
MasterMACSAxis *pAxis = NULL;
|
||||
int devStatus, isOn;
|
||||
|
||||
pAxis = (MasterMACSAxis *) (this->getAxis(pasynUser));
|
||||
if (!pAxis) {
|
||||
return asynError;
|
||||
}
|
||||
if (function == axisEnabled_) {
|
||||
devStatus = pAxis->readStatus();
|
||||
isOn = pAxis->isOn(devStatus);
|
||||
// errlogPrintf("isOn in readInt32: %d, devStatus = %d\n", isOn, devStatus);
|
||||
pAxis->setIntegerParam(enableAxis_, isOn);
|
||||
pAxis->setIntegerParam(axisEnabled_, isOn);
|
||||
*value = isOn;
|
||||
callParamCallbacks();
|
||||
return asynSuccess;
|
||||
}
|
||||
return asynMotorController::readInt32(pasynUser, value);
|
||||
}
|
||||
/*------------------------------------------------------------------------------------------*/
|
||||
|
||||
asynStatus
|
||||
MasterMACSController::writeInt32(asynUser * pasynUser,
|
||||
epicsInt32 value)
|
||||
{
|
||||
int function = pasynUser->reason;
|
||||
asynStatus status = asynSuccess;
|
||||
MasterMACSAxis *pAxis = NULL;
|
||||
char command[64] = { 0 };
|
||||
char response[64] = { 0 };
|
||||
int devStatus;
|
||||
bool moving;
|
||||
time_t startTime;
|
||||
|
||||
pAxis = (MasterMACSAxis *) this->getAxis(pasynUser);
|
||||
if (!pAxis) {
|
||||
return asynError;
|
||||
}
|
||||
|
||||
/* Set the parameter and readback in the parameter library. This may be
|
||||
* overwritten when we read back the status at the end, but that's OK */
|
||||
pAxis->setIntegerParam(function, value);
|
||||
|
||||
if(function == motorStop_){
|
||||
errlogPrintf("Stop called with value %d\n", value);
|
||||
double accel;
|
||||
getDoubleParam(pAxis->axisNo_, motorAccel_, &accel);
|
||||
status = pAxis->stop(accel);
|
||||
return status;
|
||||
}
|
||||
if (function == enableAxis_) {
|
||||
/*
|
||||
* Read the status in order to prevent execssive commands
|
||||
*/
|
||||
devStatus = pAxis->readStatus();
|
||||
if (value == 1 && !pAxis->isOn(devStatus) ) {
|
||||
/* download parameters, does not work as of now */
|
||||
/*
|
||||
sprintf(command, "%dS85=1.", pAxis->axisNo_);
|
||||
status = transactController(pAxis->axisNo_, command, response);
|
||||
*/
|
||||
|
||||
/* actually enable */
|
||||
sprintf(command, "%dS04=1", pAxis->axisNo_);
|
||||
status = transactController(pAxis->axisNo_, command, response);
|
||||
} else if(pAxis->isOn(devStatus)) {
|
||||
// only send command when necessary
|
||||
sprintf(command, "%dS04=0", pAxis->axisNo_);
|
||||
status = transactController(pAxis->axisNo_, command, response);
|
||||
} else {
|
||||
// nothing to do
|
||||
return status;
|
||||
}
|
||||
if (status == asynSuccess) {
|
||||
pAxis->updateMsgTxtFromDriver("");
|
||||
} else {
|
||||
errlogPrintf("MMACS: Failure to enable or disable axis %d",
|
||||
pAxis->axisNo_);
|
||||
}
|
||||
/*
|
||||
* wait max 2 seconds for success of the operation
|
||||
*/
|
||||
startTime = time(NULL);
|
||||
while(time(NULL) < startTime + 2.){
|
||||
// wait for the change to happen
|
||||
devStatus = pAxis->readStatus();
|
||||
// errlogPrintf("MMACS: switching enable: target, devStatus, isOn: %d, %d, %d\n", value, devStatus, pAxis->isOn(devStatus));
|
||||
if(pAxis->isOn(devStatus) == value){
|
||||
pAxis->active = true;
|
||||
pAxis->poll(&moving); // to update the Enable_RBV field
|
||||
pAxis->active = false;
|
||||
return asynSuccess;
|
||||
}
|
||||
usleep(200);
|
||||
}
|
||||
errlogPrintf("MMACS: Failed to dis/enable motor %d within 2 seconds\n", pAxis->axisNo_);
|
||||
}
|
||||
return asynMotorController::writeInt32(pasynUser, value);
|
||||
}
|
||||
|
||||
|
||||
// These are the MasterMACSAxis methods
|
||||
|
||||
/** Creates a new MasterMACSAxis object.
|
||||
* \param[in] pC Pointer to the MasterMACSController to which this axis belongs.
|
||||
* \param[in] axisNo Index number of this axis, range 0 to pC->numAxes_-1.
|
||||
*
|
||||
* Initializes register numbers, etc.
|
||||
*/
|
||||
MasterMACSAxis::MasterMACSAxis(MasterMACSController * pC, int axisNo):SINQAxis(pC, axisNo),
|
||||
pC_(pC)
|
||||
{
|
||||
hasStarted = false;
|
||||
active = false;
|
||||
errorCodeFound = 0;
|
||||
/*
|
||||
* Try to read the initial enable status of the motor
|
||||
*/
|
||||
int devStatus = readStatus();
|
||||
setIntegerParam(pC_->enableAxis_, isOn(devStatus));
|
||||
setIntegerParam(pC_->axisEnabled_, isOn(devStatus));
|
||||
lastPoll = -1;
|
||||
}
|
||||
|
||||
/** Reports on status of the axis
|
||||
* \param[in] fp The file pointer on which report information will be written
|
||||
* \param[in] level The level of report detail desired
|
||||
*
|
||||
* After printing device-specific information calls asynMotorAxis::report()
|
||||
*/
|
||||
void MasterMACSAxis::report(FILE * fp, int level)
|
||||
{
|
||||
if (level > 0) {
|
||||
fprintf(fp, " axis %d\n", axisNo_);
|
||||
}
|
||||
}
|
||||
|
||||
int MasterMACSAxis::readStatus()
|
||||
{
|
||||
char command[COMLEN], reply[COMLEN], *pPtr;
|
||||
float fval;
|
||||
int status;
|
||||
|
||||
/*
|
||||
* The MasterMACS sends invalid responses with a low frequency.
|
||||
* Therefore I send cached status responses in such a case in order
|
||||
* to help the logic everywhere else in the code.
|
||||
*/
|
||||
sprintf(command, "%dR10", axisNo_);
|
||||
status = pC_->transactController(axisNo_, command, reply);
|
||||
if (status == asynError) {
|
||||
return oldStatus;
|
||||
}
|
||||
pPtr = strstr(reply, "=");
|
||||
if(pPtr) {
|
||||
sscanf(pPtr + 1, "%f", &fval);
|
||||
oldStatus = (int) fval;
|
||||
return oldStatus;
|
||||
} else {
|
||||
errlogPrintf("MMACS: invalid status reponse %s on axis %d\n", reply, axisNo_);
|
||||
return oldStatus;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int MasterMACSAxis::isOn(int axisStatus)
|
||||
{
|
||||
if (CHECK_BIT(axisStatus, 0) && CHECK_BIT(axisStatus, 1)) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
asynStatus
|
||||
MasterMACSAxis::move(double position, int relative, double minVelocity,
|
||||
double maxVelocity, double acceleration)
|
||||
{
|
||||
asynStatus status = asynSuccess;
|
||||
char command[COMLEN], reply[COMLEN];
|
||||
int devStatus;
|
||||
|
||||
//errlogPrintf("minVelocity = %f, maxVelocity = %f\n", minVelocity, maxVelocity);
|
||||
memset(command, 0, COMLEN * sizeof(char));
|
||||
|
||||
/* clear motor error message */
|
||||
updateMsgTxtFromDriver("");
|
||||
|
||||
/*
|
||||
* reset error code
|
||||
*/
|
||||
if(errorCodeFound){
|
||||
sprintf(command, "%dS17=0", axisNo_);
|
||||
status = pC_->transactController(axisNo_, command, reply);
|
||||
errorCodeFound = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* only start if the thing is On
|
||||
*/
|
||||
devStatus = readStatus();
|
||||
|
||||
/*
|
||||
* set speed
|
||||
*/
|
||||
/* temporarily disabled in order to get the basic logic right
|
||||
sprintf(command, "%dS05=%f", axisNo_, maxVelocity / 1000);
|
||||
status = pC_->transactController(axisNo_, command, reply);
|
||||
*/
|
||||
|
||||
if (relative) {
|
||||
position += this->position;
|
||||
}
|
||||
|
||||
if(isOn(devStatus) && active == false) {
|
||||
errlogPrintf("Starting axis %d with destination %f\n", axisNo_,
|
||||
position / 1000.);
|
||||
|
||||
/* send target position */
|
||||
sprintf(command, "%dS02= %.3f", axisNo_, position / 1000.);
|
||||
status = pC_->transactController(axisNo_, command, reply);
|
||||
if (status == asynError) {
|
||||
errlogPrintf("MMACS: failed to set target on %d\n", axisNo_);
|
||||
updateMsgTxtFromDriver("Failed to send target position");
|
||||
return status;
|
||||
}
|
||||
|
||||
/* send move command */
|
||||
sprintf(command, "%dS00=1", axisNo_);
|
||||
status = pC_->transactController(axisNo_, command, reply);
|
||||
if (status == asynError) {
|
||||
errlogPrintf("MMACS: failed to start axis %d\n", axisNo_);
|
||||
updateMsgTxtFromDriver("Failed to start axis");
|
||||
return status;
|
||||
}
|
||||
hasStarted = true;
|
||||
homing = 0;
|
||||
active = true;
|
||||
usleep(500);
|
||||
lastPositionUpdate = time(NULL);
|
||||
} else {
|
||||
errlogPrintf("MMACS: axis %d disabled, cannot start\n", axisNo_);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
asynStatus
|
||||
MasterMACSAxis::home(double minVelocity, double maxVelocity,
|
||||
double acceleration, int forwards)
|
||||
{
|
||||
asynStatus status;
|
||||
char command[COMLEN], reply[COMLEN];
|
||||
int devStatus;
|
||||
|
||||
memset(command, 0, COMLEN * sizeof(char));
|
||||
|
||||
/*
|
||||
* test if the thing is On
|
||||
*/
|
||||
devStatus = readStatus();
|
||||
updateMsgTxtFromDriver("");
|
||||
|
||||
if(isOn(devStatus)){
|
||||
sprintf(command, "%dS00=9", axisNo_);
|
||||
homing = 1;
|
||||
status = pC_->transactController(axisNo_, command, reply);
|
||||
hasStarted = true;
|
||||
active = true;
|
||||
lastPositionUpdate = time(NULL);
|
||||
return status;
|
||||
} else {
|
||||
errlogPrintf("MMACS: cannot home disabled axis %d\n", axisNo_);
|
||||
return asynError;
|
||||
}
|
||||
}
|
||||
|
||||
asynStatus
|
||||
MasterMACSAxis::moveVelocity(double minVelocity, double maxVelocity,
|
||||
double acceleration)
|
||||
{
|
||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
errlogSevPrintf(errlogMajor,
|
||||
"This controller does not support the jog feature");
|
||||
return asynError;
|
||||
}
|
||||
|
||||
asynStatus MasterMACSAxis::stop(double acceleration)
|
||||
{
|
||||
asynStatus status = asynSuccess;
|
||||
char command[COMLEN], reply[COMLEN];
|
||||
int devStatus;
|
||||
|
||||
memset(command, 0, COMLEN * sizeof(char));
|
||||
|
||||
devStatus = readStatus();
|
||||
if(!CHECK_BIT(devStatus, 10)) {
|
||||
// only try to stop when running ...
|
||||
sprintf(command, "%dS00=8", axisNo_);
|
||||
status = pC_->transactController(axisNo_, command, reply);
|
||||
errlogPrintf("Sent STOP on Axis %d\n", axisNo_);
|
||||
updateMsgTxtFromDriver("Axis interrupted");
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
asynStatus MasterMACSAxis::setPosition(double position)
|
||||
{
|
||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
errlogSevPrintf(errlogMajor,
|
||||
"This controller does not support setting position");
|
||||
updateMsgTxtFromDriver("Controller does not support setPosition");
|
||||
return asynError;
|
||||
}
|
||||
|
||||
asynStatus MasterMACSAxis::setClosedLoop(bool closedLoop)
|
||||
{
|
||||
/*
|
||||
This belongs into the Kingdom of Electronics.
|
||||
We do not do this.
|
||||
*/
|
||||
|
||||
return asynError;
|
||||
}
|
||||
|
||||
/** Polls the axis.
|
||||
* This function reads the motor position, the limit status, the home status, the moving status,
|
||||
* and the drive power-on status.
|
||||
* It calls setIntegerParam() and setDoubleParam() for each item that it polls,
|
||||
* and then calls callParamCallbacks() at the end.
|
||||
* \param[out] moving A flag that is set indicating that the axis is moving (true) or done (false). */
|
||||
asynStatus MasterMACSAxis::poll(bool * moving)
|
||||
{
|
||||
asynStatus comStatus = asynSuccess;
|
||||
char command[COMLEN], reply[COMLEN], *pPtr, buffer[80];
|
||||
unsigned int errCode, derCode, devStatus = 0;
|
||||
float errStatus;
|
||||
struct tm* tm_info;
|
||||
time_t timer;
|
||||
|
||||
/*
|
||||
* EPICS always polls all motors on a controller when one motor is active.
|
||||
* In order to reduce load on the controller we check if we are active and
|
||||
* if not return old stuff
|
||||
*/
|
||||
if(active == false && time(NULL) < lastPoll + pC_->idlePollPeriod_) {
|
||||
*moving = false;
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
timer = time(NULL);
|
||||
tm_info = localtime(&timer);
|
||||
|
||||
strftime(buffer, 26, "%Y-%m-%d %H:%M:%S", tm_info);
|
||||
errlogPrintf("poll called at %s on axis %d \n",
|
||||
buffer, axisNo_ );
|
||||
lastPoll = time(NULL);
|
||||
|
||||
setIntegerParam(pC_->motorStatusProblem_, false);
|
||||
memset(command, 0, COMLEN * sizeof(char));
|
||||
|
||||
// Read the current motor position
|
||||
sprintf(command, "%dR12", axisNo_);
|
||||
comStatus = pC_->transactController(axisNo_, command, reply);
|
||||
if(comStatus != asynError) {
|
||||
pPtr = strstr(reply, "=");
|
||||
if(pPtr) {
|
||||
sscanf(pPtr + 1, "%lf", &position);
|
||||
setDoubleParam(pC_->motorPosition_, position * 1000.);
|
||||
setDoubleParam(pC_->motorEncoderPosition_, position * 1000.);
|
||||
/*
|
||||
* keep track of position in order to check for a stuck motor later
|
||||
*/
|
||||
if(ABS(position - oldPosition) > 1){
|
||||
oldPosition = position;
|
||||
lastPositionUpdate = time(NULL);
|
||||
}
|
||||
} else {
|
||||
errlogPrintf("MMACS: Invalid response asking position on %d\n", axisNo_);
|
||||
}
|
||||
} else {
|
||||
errlogPrintf("MMACS: communication problem reading axis %d position\n", axisNo_);
|
||||
*moving = false;
|
||||
comStatus = asynError;
|
||||
goto skip;
|
||||
}
|
||||
|
||||
// Read the overall status of this motor */
|
||||
devStatus = readStatus();
|
||||
if(debug || timeDebug) {
|
||||
errlogPrintf("Axis %d, position %lf, devStatus %d\n", axisNo_,
|
||||
position, devStatus);
|
||||
}
|
||||
|
||||
// Check for the thing being in a bad state
|
||||
if(CHECK_BIT(devStatus, 6)) {
|
||||
*moving = false;
|
||||
active = false;
|
||||
setIntegerParam(pC_->motorStatusDone_, true);
|
||||
updateMsgTxtFromDriver("AXIS dead");
|
||||
goto skip;
|
||||
}
|
||||
|
||||
|
||||
setIntegerParam(pC_->enableAxis_, isOn(devStatus));
|
||||
setIntegerParam(pC_->axisEnabled_, isOn(devStatus));
|
||||
|
||||
// Check if the motor is disabled
|
||||
if (!isOn(devStatus)) {
|
||||
updateMsgTxtFromDriver("Axis disabled");
|
||||
*moving = false;
|
||||
active = false;
|
||||
setIntegerParam(pC_->motorStatusDone_, true);
|
||||
goto skip;
|
||||
}
|
||||
|
||||
/*
|
||||
* if the motor has never run, the status bit 10 is invalid
|
||||
*/
|
||||
if (!hasStarted) {
|
||||
*moving = false;
|
||||
setIntegerParam(pC_->motorStatusDone_, true);
|
||||
active = false;
|
||||
goto skip;
|
||||
}
|
||||
|
||||
/*
|
||||
* We may have a valid status bit...
|
||||
*/
|
||||
if (!CHECK_BIT(devStatus, 10)) {
|
||||
/* we are still creeping along .... */
|
||||
*moving = true;
|
||||
setIntegerParam(pC_->motorStatusDone_, false);
|
||||
if(time(NULL) > lastPositionUpdate + 120) {
|
||||
// we are detecting a stuck motor
|
||||
errlogPrintf("MMACS: axis %d is STUCK!!\n", axisNo_);
|
||||
updateMsgTxtFromDriver("Axis STUCK!!");
|
||||
setIntegerParam(pC_->motorStatusDone_, true);
|
||||
*moving = false;
|
||||
active = false;
|
||||
}
|
||||
goto skip;
|
||||
}
|
||||
|
||||
|
||||
/*we are done moving */
|
||||
*moving = false;
|
||||
active = false;
|
||||
setIntegerParam(pC_->motorStatusDone_, true);
|
||||
updateMsgTxtFromDriver("");
|
||||
|
||||
/* when homing, set the proper flag */
|
||||
if (homing) {
|
||||
setIntegerParam(pC_->motorStatusAtHome_, true);
|
||||
}
|
||||
|
||||
/* check for limit switches*/
|
||||
setIntegerParam(pC_->motorStatusLowLimit_, false);
|
||||
setIntegerParam(pC_->motorStatusHighLimit_, false);
|
||||
if (CHECK_BIT(devStatus, 11)) {
|
||||
errlogSevPrintf(errlogMajor, "Limit bit in status code %d", axisNo_);
|
||||
/* guessing which limit has been hit ... */
|
||||
if (position > 0) {
|
||||
updateMsgTxtFromDriver("Hit positive limit switch");
|
||||
setIntegerParam(pC_->motorStatusHighLimit_, true);
|
||||
} else {
|
||||
updateMsgTxtFromDriver("Hit negative limit switch");
|
||||
setIntegerParam(pC_->motorStatusLowLimit_, true);
|
||||
}
|
||||
goto skip;
|
||||
}
|
||||
|
||||
/* check for error conditions */
|
||||
if (CHECK_BIT(devStatus, 3)) {
|
||||
/* read error codes */
|
||||
sprintf(command, "%dR11", axisNo_);
|
||||
comStatus = pC_->transactController(axisNo_, command, reply);
|
||||
if (comStatus != asynError) {
|
||||
pPtr = strstr(reply, "=");
|
||||
if(pPtr) {
|
||||
sscanf(pPtr + 1, "%f", &errStatus);
|
||||
errCode = (unsigned int) errStatus;
|
||||
|
||||
} else {
|
||||
errlogPrintf("MMACS: axis %d received invalid reply asking for error code \n", axisNo_);
|
||||
errCode = 0;
|
||||
goto skip;
|
||||
}
|
||||
} else {
|
||||
errlogPrintf("MMACS: axis %d failed reading error code \n", axisNo_);
|
||||
goto skip;
|
||||
}
|
||||
|
||||
sprintf(command, "%dR18", axisNo_);
|
||||
comStatus = pC_->transactController(axisNo_, command, reply);
|
||||
if (comStatus != asynError) {
|
||||
pPtr = strstr(reply, "=");
|
||||
if(pPtr) {
|
||||
sscanf(pPtr + 1, "%f", &errStatus);
|
||||
derCode = (unsigned int) errStatus;
|
||||
} else {
|
||||
errlogPrintf("MMACS: malformed reply for R18: %s, on axis %d\n", reply, axisNo_);
|
||||
derCode = 0;
|
||||
goto skip;
|
||||
}
|
||||
} else {
|
||||
errlogPrintf("MMACS: axis %d failed reading extended error code R18 \n", axisNo_);
|
||||
goto skip;
|
||||
}
|
||||
|
||||
if(debug) {
|
||||
errlogPrintf("Axis %d, errCode(R11) %d, derCode(R18) %d\n", axisNo_,
|
||||
errCode, derCode);
|
||||
}
|
||||
|
||||
if (errCode == 0) {
|
||||
errlogSevPrintf(errlogMajor,
|
||||
"Fault bit in status code, but no error code on %d\n", axisNo_);
|
||||
updateMsgTxtFromDriver ("Fault bit in status code without error code");
|
||||
goto skip;
|
||||
}
|
||||
errorCodeFound = 1;
|
||||
|
||||
if (CHECK_BIT(errCode, 0)) {
|
||||
errlogSevPrintf(errlogMajor, "CAN error on %d", axisNo_);
|
||||
updateMsgTxtFromDriver("CAN error");
|
||||
} else if (CHECK_BIT(errCode, 1)) {
|
||||
errlogSevPrintf(errlogMajor, "Short circuit on %d", axisNo_);
|
||||
updateMsgTxtFromDriver("Short circuit");
|
||||
} else if (CHECK_BIT(errCode, 2)) {
|
||||
errlogSevPrintf(errlogMajor, "Invalide Setup on %d", axisNo_);
|
||||
updateMsgTxtFromDriver("Invalid Setup");
|
||||
} else if (CHECK_BIT(errCode, 3)) {
|
||||
errlogSevPrintf(errlogMajor, "Control error on %d", axisNo_);
|
||||
updateMsgTxtFromDriver("Control error");
|
||||
} else if (CHECK_BIT(errCode, 4)) {
|
||||
errlogSevPrintf(errlogMajor, "CAN communication error on %d",
|
||||
axisNo_);
|
||||
updateMsgTxtFromDriver("CAN communication error");
|
||||
} else if (CHECK_BIT(errCode, 5)) {
|
||||
errlogSevPrintf(errlogMajor, "Feedback error on %d", axisNo_);
|
||||
updateMsgTxtFromDriver("Feedback error");
|
||||
} else if (CHECK_BIT(errCode, 6)) {
|
||||
updateMsgTxtFromDriver("Hit positive limit switch");
|
||||
setIntegerParam(pC_->motorStatusHighLimit_, true);
|
||||
setIntegerParam(pC_->motorStatusProblem_, false);
|
||||
} else if (CHECK_BIT(errCode, 7)) {
|
||||
updateMsgTxtFromDriver("Hit negative limit switch");
|
||||
setIntegerParam(pC_->motorStatusLowLimit_, true);
|
||||
setIntegerParam(pC_->motorStatusProblem_, false);
|
||||
} else if (CHECK_BIT(errCode, 8)) {
|
||||
errlogSevPrintf(errlogMajor, "Over current %d", axisNo_);
|
||||
updateMsgTxtFromDriver("Over current");
|
||||
} else if (CHECK_BIT(errCode, 9)) {
|
||||
errlogSevPrintf(errlogMajor, "I2T protection on %d", axisNo_);
|
||||
updateMsgTxtFromDriver("I2t protection");
|
||||
} else if (CHECK_BIT(errCode, 10)) {
|
||||
errlogSevPrintf(errlogMajor, "Over heated motor on %d", axisNo_);
|
||||
updateMsgTxtFromDriver("Motor overheated");
|
||||
} else if (CHECK_BIT(errCode, 11)) {
|
||||
errlogSevPrintf(errlogMajor, "Over temperature drive on %d",
|
||||
axisNo_);
|
||||
updateMsgTxtFromDriver("Over temperature drive");
|
||||
} else if (CHECK_BIT(errCode, 12)) {
|
||||
errlogSevPrintf(errlogMajor, "Over voltage on %d", axisNo_);
|
||||
updateMsgTxtFromDriver("Over voltage");
|
||||
} else if (CHECK_BIT(errCode, 13)) {
|
||||
errlogSevPrintf(errlogMajor, "Under voltage on %d", axisNo_);
|
||||
updateMsgTxtFromDriver("Under voltage");
|
||||
} else if (CHECK_BIT(errCode, 14)) {
|
||||
errlogSevPrintf(errlogMajor, "Command error on %d", axisNo_);
|
||||
updateMsgTxtFromDriver("Command error");
|
||||
} else if (CHECK_BIT(errCode, 15)) {
|
||||
errlogSevPrintf(errlogMajor, "Motor disabled on %d", axisNo_);
|
||||
updateMsgTxtFromDriver("Motor disabled");
|
||||
}
|
||||
}
|
||||
|
||||
skip:
|
||||
callParamCallbacks();
|
||||
return comStatus;
|
||||
}
|
||||
|
||||
/** Code for iocsh registration */
|
||||
static const iocshArg
|
||||
MasterMACSCreateControllerArg0 = { "Port name", iocshArgString };
|
||||
|
||||
static const iocshArg
|
||||
MasterMACSCreateControllerArg1 =
|
||||
{ "MasterMACS port name", iocshArgString };
|
||||
|
||||
static const iocshArg
|
||||
MasterMACSCreateControllerArg2 = { "Number of axes", iocshArgInt };
|
||||
|
||||
static const iocshArg
|
||||
MasterMACSCreateControllerArg3 = { "idlePoll", iocshArgInt };
|
||||
|
||||
static const iocshArg
|
||||
MasterMACSCreateControllerArg4 = { "busyPoll", iocshArgInt };
|
||||
|
||||
|
||||
static const iocshArg *const
|
||||
MasterMACSCreateControllerArgs[] = { &MasterMACSCreateControllerArg0,
|
||||
&MasterMACSCreateControllerArg1,
|
||||
&MasterMACSCreateControllerArg2,
|
||||
&MasterMACSCreateControllerArg3,
|
||||
&MasterMACSCreateControllerArg4
|
||||
};
|
||||
|
||||
static const iocshFuncDef
|
||||
MasterMACSCreateControllerDef =
|
||||
{ "MasterMACSCreateController", 5, MasterMACSCreateControllerArgs };
|
||||
static void MasterMACSCreateContollerCallFunc(const iocshArgBuf * args)
|
||||
{
|
||||
MasterMACSCreateController(args[0].sval, args[1].sval, args[2].ival,
|
||||
args[3].ival, args[4].ival);
|
||||
}
|
||||
|
||||
/**
|
||||
* C wrapper for the MasterMACSAxis constructor.
|
||||
* See MasterMAXSAxis::MasterMACSAxis.
|
||||
*
|
||||
*/
|
||||
asynStatus MasterMACSCreateAxis(const char *MasterMACSPort, /* specify which controller by port name */
|
||||
int axis)
|
||||
{ /* axis number (start from 1). */
|
||||
MasterMACSController *pC;
|
||||
MasterMACSAxis *pAxis;
|
||||
|
||||
static const char *functionName = "MasterMACSCreateAxis";
|
||||
|
||||
pC = (MasterMACSController *) findAsynPortDriver(MasterMACSPort);
|
||||
if (!pC) {
|
||||
printf("%s:%s: Error port %s not found\n", "MasterMACSDriver",
|
||||
functionName, MasterMACSPort);
|
||||
return asynError;
|
||||
}
|
||||
|
||||
pC->lock();
|
||||
pAxis = new MasterMACSAxis(pC, axis);
|
||||
pAxis = NULL;
|
||||
pC->unlock();
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
/* MasterMACSCreateAxis */
|
||||
static const iocshArg
|
||||
MasterMACSCreateAxisArg0 = { "Controller port name", iocshArgString };
|
||||
|
||||
static const iocshArg
|
||||
MasterMACSCreateAxisArg1 = { "Axis number", iocshArgInt };
|
||||
|
||||
static const iocshArg *const
|
||||
MasterMACSCreateAxisArgs[] = { &MasterMACSCreateAxisArg0,
|
||||
&MasterMACSCreateAxisArg1
|
||||
};
|
||||
|
||||
static const iocshFuncDef
|
||||
configMasterMACSAxis =
|
||||
{ "MasterMACSCreateAxis", 2, MasterMACSCreateAxisArgs };
|
||||
|
||||
static void configMasterMACSAxisCallFunc(const iocshArgBuf * args)
|
||||
{
|
||||
MasterMACSCreateAxis(args[0].sval, args[1].ival);
|
||||
}
|
||||
|
||||
static void MasterMACSRegister(void)
|
||||
{
|
||||
iocshRegister(&MasterMACSCreateControllerDef,
|
||||
MasterMACSCreateContollerCallFunc);
|
||||
iocshRegister(&configMasterMACSAxis, configMasterMACSAxisCallFunc);
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
epicsExportRegistrar(MasterMACSRegister);
|
||||
}
|
77
sinqEPICSApp/src/MasterMACSDriver.h
Normal file
77
sinqEPICSApp/src/MasterMACSDriver.h
Normal file
@ -0,0 +1,77 @@
|
||||
/*
|
||||
Driver for the MasterMACS motor controllers used at SINQ.
|
||||
For documentation see the standard manuals area for SINQ or
|
||||
Marcel Schildt.
|
||||
|
||||
The MasterMACS has a special line protocol which is implemented in
|
||||
drvAsynMMACSPort.c. The driver will not work with a standard asyn IPPort,
|
||||
only with the special one.
|
||||
|
||||
Mark Koennecke, March, August, 2023
|
||||
*/
|
||||
|
||||
#include "SINQController.h"
|
||||
#include "SINQAxis.h"
|
||||
|
||||
#define COMLEN 50
|
||||
|
||||
class MasterMACSAxis : public SINQAxis
|
||||
{
|
||||
public:
|
||||
/* These are the methods we override from the base class */
|
||||
MasterMACSAxis(class MasterMACSController *pC, int axis);
|
||||
void report(FILE *fp, int level);
|
||||
asynStatus move(double position, int relative, double min_velocity, double max_velocity, double acceleration);
|
||||
asynStatus moveVelocity(double min_velocity, double max_velocity, double acceleration);
|
||||
asynStatus home(double min_velocity, double max_velocity, double acceleration, int forwards);
|
||||
asynStatus stop(double acceleration);
|
||||
asynStatus poll(bool *moving);
|
||||
asynStatus setPosition(double position);
|
||||
asynStatus setClosedLoop(bool closedLoop);
|
||||
|
||||
private:
|
||||
MasterMACSController *pC_; /**< Pointer to the asynMotorController to which this axis belongs.
|
||||
* Abbreviated because it is used very frequently */
|
||||
double position;
|
||||
double oldPosition;
|
||||
time_t lastPositionUpdate;
|
||||
int homing;
|
||||
int hasStarted; /* The motor status is invalid if the thing has not run once */
|
||||
int isOn(int axisStatus);
|
||||
int readStatus();
|
||||
int errorCodeFound;
|
||||
int oldStatus;
|
||||
|
||||
bool active;
|
||||
time_t lastPoll;
|
||||
|
||||
friend class MasterMACSController;
|
||||
};
|
||||
|
||||
|
||||
#define EnableAxisString "ENABLE_AXIS"
|
||||
#define AxisEnabledString "AXIS_ENABLED"
|
||||
|
||||
class MasterMACSController : public SINQController {
|
||||
public:
|
||||
MasterMACSController(const char *portName, const char *MasterMACSPortName,
|
||||
int numAxes, int idlePoll, int busyPoll);
|
||||
|
||||
void report(FILE *fp, int level);
|
||||
MasterMACSAxis* getAxis(asynUser *pasynUser);
|
||||
MasterMACSAxis* getAxis(int axisNo);
|
||||
|
||||
// overloaded because we want to enable/disable the motor
|
||||
asynStatus readInt32(asynUser *pasynUser, epicsInt32 *value);
|
||||
asynStatus writeInt32(asynUser *pasynUser, epicsInt32 value);
|
||||
|
||||
friend class MasterMACSAxis;
|
||||
private:
|
||||
asynUser *pasynUserController_;
|
||||
MasterMACSAxis **pAxes_; /**< Array of pointers to axis objects */
|
||||
|
||||
|
||||
asynStatus transactController(int axis, char command[COMLEN], char reply[COMLEN]);
|
||||
int enableAxis_;
|
||||
int axisEnabled_;
|
||||
};
|
@ -14,6 +14,9 @@ list to the motor controller constructor.
|
||||
Mark Koennecke
|
||||
July 2015
|
||||
|
||||
Modified to use the MsgTxt field for SINQ
|
||||
|
||||
Mark Koennecke, January 2019
|
||||
*/
|
||||
|
||||
|
||||
@ -43,12 +46,7 @@ July 2015
|
||||
* \param[in] NanotecPortName The name of the drvAsynSerialPort that was created previously to connect to the Nanotec controller
|
||||
*/
|
||||
NanotecController::NanotecController(const char *portName, const char *NanotecPortName, int motCount, const char *bus)
|
||||
: asynMotorController(portName, motCount+1, 0,
|
||||
0, // No additional interfaces beyond those in base class
|
||||
0, // No additional callback interfaces beyond those in base class
|
||||
ASYN_CANBLOCK | ASYN_MULTIDEVICE,
|
||||
1, // autoconnect
|
||||
0, 0) // Default priority and stack size
|
||||
: SINQController(portName, NanotecPortName, motCount+1)
|
||||
{
|
||||
int axis, busAddress;
|
||||
asynStatus status;
|
||||
@ -136,11 +134,11 @@ NanotecAxis* NanotecController::getAxis(int axisNo)
|
||||
* Initializes register numbers, etc.
|
||||
*/
|
||||
NanotecAxis::NanotecAxis(NanotecController *pC, int axisNo, int busAddress)
|
||||
: asynMotorAxis(pC, axisNo),
|
||||
: SINQAxis(pC, axisNo),
|
||||
pC_(pC)
|
||||
{
|
||||
this->busAddress = busAddress;
|
||||
|
||||
next_poll = -1;
|
||||
}
|
||||
|
||||
|
||||
@ -162,17 +160,21 @@ void NanotecAxis::report(FILE *fp, int level)
|
||||
//asynMotorAxis::report(fp, level);
|
||||
}
|
||||
|
||||
asynStatus NanotecController::transactController(char command[COMLEN], char reply[COMLEN])
|
||||
asynStatus NanotecController::transactController(int axisNo, char command[COMLEN], char reply[COMLEN])
|
||||
{
|
||||
asynStatus status;
|
||||
size_t in, out;
|
||||
int reason;
|
||||
SINQAxis *axis = getAxis(axisNo);
|
||||
|
||||
pasynOctetSyncIO->flush(pasynUserController_);
|
||||
|
||||
status = pasynOctetSyncIO->writeRead(pasynUserController_, command, strlen(command),
|
||||
reply,COMLEN, 1.,&out,&in,&reason);
|
||||
if(status != asynSuccess){
|
||||
if(axis != NULL){
|
||||
axis->updateMsgTxtFromDriver("Lost connection to motor controller");
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
@ -200,6 +202,8 @@ asynStatus NanotecAxis::move(double position, int relative, double minVelocity,
|
||||
size_t in, out;
|
||||
int reason;
|
||||
|
||||
updateMsgTxtFromDriver("");
|
||||
|
||||
// status = sendAccelAndVelocity(acceleration, maxVelocity);
|
||||
|
||||
if (relative) {
|
||||
@ -215,7 +219,7 @@ asynStatus NanotecAxis::move(double position, int relative, double minVelocity,
|
||||
set mode
|
||||
*/
|
||||
snprintf(command,sizeof(command),"#%dp2",busAddress);
|
||||
status = pC_->transactController(command,reply);
|
||||
status = pC_->transactController(axisNo_,command,reply);
|
||||
if(status != asynSuccess){
|
||||
return status;
|
||||
}
|
||||
@ -224,7 +228,7 @@ asynStatus NanotecAxis::move(double position, int relative, double minVelocity,
|
||||
set target
|
||||
*/
|
||||
snprintf(command,sizeof(command),"#%ds%d",busAddress, (int)position);
|
||||
status = pC_->transactController(command,reply);
|
||||
status = pC_->transactController(axisNo_,command,reply);
|
||||
if(status != asynSuccess){
|
||||
return status;
|
||||
}
|
||||
@ -233,7 +237,7 @@ asynStatus NanotecAxis::move(double position, int relative, double minVelocity,
|
||||
and start..
|
||||
*/
|
||||
snprintf(command,sizeof(command),"#%dA",busAddress);
|
||||
status = pC_->transactController(command,reply);
|
||||
status = pC_->transactController(axisNo_,command,reply);
|
||||
if(status != asynSuccess){
|
||||
return status;
|
||||
}
|
||||
@ -250,11 +254,13 @@ asynStatus NanotecAxis::home(double minVelocity, double maxVelocity, double acce
|
||||
|
||||
setIntegerParam(pC_->motorStatusAtHome_, false);
|
||||
|
||||
updateMsgTxtFromDriver("");
|
||||
|
||||
/*
|
||||
reset positioning errors
|
||||
*/
|
||||
snprintf(command,sizeof(command),"#%dD",busAddress);
|
||||
status = pC_->transactController(command,reply);
|
||||
status = pC_->transactController(axisNo_,command,reply);
|
||||
if(status != asynSuccess){
|
||||
return status;
|
||||
}
|
||||
@ -263,7 +269,7 @@ asynStatus NanotecAxis::home(double minVelocity, double maxVelocity, double acce
|
||||
set mode
|
||||
*/
|
||||
snprintf(command,sizeof(command),"#%dp4",busAddress);
|
||||
status = pC_->transactController(command,reply);
|
||||
status = pC_->transactController(axisNo_,command,reply);
|
||||
if(status != asynSuccess){
|
||||
return status;
|
||||
}
|
||||
@ -273,7 +279,7 @@ asynStatus NanotecAxis::home(double minVelocity, double maxVelocity, double acce
|
||||
set direction
|
||||
*/
|
||||
snprintf(command,sizeof(command),"#%dd0",busAddress);
|
||||
status = pC_->transactController(command,reply);
|
||||
status = pC_->transactController(axisNo_,command,reply);
|
||||
if(status != asynSuccess){
|
||||
return status;
|
||||
}
|
||||
@ -283,7 +289,7 @@ asynStatus NanotecAxis::home(double minVelocity, double maxVelocity, double acce
|
||||
and start..
|
||||
*/
|
||||
snprintf(command,sizeof(command),"#%dA",busAddress);
|
||||
status = pC_->transactController(command,reply);
|
||||
status = pC_->transactController(axisNo_,command,reply);
|
||||
if(status != asynSuccess){
|
||||
return status;
|
||||
}
|
||||
@ -304,6 +310,7 @@ asynStatus NanotecAxis::moveVelocity(double minVelocity, double maxVelocity, dou
|
||||
// "%s: minVelocity=%f, maxVelocity=%f, acceleration=%f\n",
|
||||
// functionName, minVelocity, maxVelocity, acceleration);
|
||||
|
||||
updateMsgTxtFromDriver("");
|
||||
|
||||
|
||||
if (maxVelocity > 0.) {
|
||||
@ -326,7 +333,7 @@ asynStatus NanotecAxis::stop(double acceleration )
|
||||
char command[COMLEN], reply[COMLEN];
|
||||
|
||||
sprintf(command, "#%dS1", busAddress);
|
||||
status = pC_->transactController(command,reply);
|
||||
status = pC_->transactController(axisNo_,command,reply);
|
||||
errlogPrintf("Sent STOP on Axis %d\n", axisNo_);
|
||||
|
||||
return status;
|
||||
@ -338,8 +345,10 @@ asynStatus NanotecAxis::setPosition(double position)
|
||||
//static const char *functionName = "NanotecAxis::setPosition";
|
||||
char command[COMLEN], reply[COMLEN];
|
||||
|
||||
updateMsgTxtFromDriver("");
|
||||
|
||||
sprintf(command, "#%dD%d", busAddress, (int)position);
|
||||
status = pC_->transactController(command,reply);
|
||||
status = pC_->transactController(axisNo_,command,reply);
|
||||
next_poll = -1;
|
||||
|
||||
return status;
|
||||
@ -368,7 +377,7 @@ asynStatus NanotecAxis::poll(bool *moving)
|
||||
asynStatus comStatus;
|
||||
char command[COMLEN], reply[COMLEN];
|
||||
char *pPtr;
|
||||
int posVal, statVal;
|
||||
int posVal, statVal, count = 0;
|
||||
double lowLim, highLim;
|
||||
|
||||
|
||||
@ -380,28 +389,40 @@ asynStatus NanotecAxis::poll(bool *moving)
|
||||
|
||||
// Read the current motor position
|
||||
sprintf(command,"#%dC", busAddress);
|
||||
comStatus = pC_->transactController(command,reply);
|
||||
comStatus = pC_->transactController(axisNo_,command,reply);
|
||||
if(comStatus) goto skip;
|
||||
|
||||
pPtr = strchr(reply,'C');
|
||||
pPtr++;
|
||||
posVal = atoi(pPtr);
|
||||
if(pPtr){
|
||||
pPtr++;
|
||||
count = sscanf(pPtr,"%d", &posVal);
|
||||
}
|
||||
if(pPtr == NULL || count < 1) {
|
||||
errlogPrintf("Invalid response %s for #C received for axis %d, address %d\n", reply, axisNo_, busAddress);
|
||||
return asynError;
|
||||
}
|
||||
|
||||
errlogPrintf("Axis %d, reply %s, position %d\n", axisNo_, reply, posVal);
|
||||
//errlogPrintf("Axis %d, reply %s, position %d\n", axisNo_, reply, posVal);
|
||||
setDoubleParam(pC_->motorPosition_, (double)posVal);
|
||||
//setDoubleParam(pC_->motorEncoderPosition_, position);
|
||||
|
||||
|
||||
// Read the moving status of this motor
|
||||
sprintf(command,"#%d$",busAddress);
|
||||
comStatus = pC_->transactController(command,reply);
|
||||
comStatus = pC_->transactController(axisNo_,command,reply);
|
||||
if(comStatus) goto skip;
|
||||
|
||||
pPtr = strchr(reply,'$');
|
||||
pPtr++;
|
||||
statVal = atoi(pPtr);
|
||||
errlogPrintf("Axis %d, reply %s, statVal = %d\n",
|
||||
axisNo_, reply, statVal);
|
||||
if(pPtr) {
|
||||
pPtr++;
|
||||
count = sscanf(pPtr, "%d", &statVal);
|
||||
}
|
||||
if(pPtr == NULL || count < 1) {
|
||||
errlogPrintf("Invalid response %s for #$ received for axis %d busAddress %d\n", reply, axisNo_, busAddress);
|
||||
return asynError;
|
||||
}
|
||||
//errlogPrintf("Axis %d, reply %s, statVal = %d\n",
|
||||
// axisNo_, reply, statVal);
|
||||
|
||||
setIntegerParam(pC_->motorStatusDone_, false);
|
||||
*moving = true;
|
||||
@ -438,10 +459,13 @@ asynStatus NanotecAxis::poll(bool *moving)
|
||||
setIntegerParam(pC_->motorStatusDone_, true);
|
||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
errlogSevPrintf(errlogMajor, "Limit or other positioning problem at %d", axisNo_);
|
||||
updateMsgTxtFromDriver("Positioning problem");
|
||||
if(ABS(posVal - lowLim) < ABS(posVal - highLim)){
|
||||
setIntegerParam(pC_->motorStatusLowLimit_, true);
|
||||
updateMsgTxtFromDriver("Low Limit Hit");
|
||||
} else {
|
||||
setIntegerParam(pC_->motorStatusHighLimit_, true);
|
||||
updateMsgTxtFromDriver("High Limit Hit");
|
||||
}
|
||||
*moving = false;
|
||||
}
|
||||
|
@ -5,15 +5,18 @@ USAGE... Motor driver support for the Nanotec SMCI controller.
|
||||
Mark Koennecke
|
||||
July 2015
|
||||
|
||||
Modified to use the MsgTxt field for SINQ
|
||||
|
||||
Mark Koennecke, January 2019
|
||||
*/
|
||||
|
||||
#include "asynMotorController.h"
|
||||
#include "asynMotorAxis.h"
|
||||
#include "SINQController.h"
|
||||
#include "SINQAxis.h"
|
||||
|
||||
#define COMLEN 80
|
||||
#define MAXMOT 99
|
||||
|
||||
class NanotecAxis : public asynMotorAxis
|
||||
class NanotecAxis : public SINQAxis
|
||||
{
|
||||
public:
|
||||
/* These are the methods we override from the base class */
|
||||
@ -38,7 +41,7 @@ private:
|
||||
friend class NanotecController;
|
||||
};
|
||||
|
||||
class NanotecController : public asynMotorController {
|
||||
class NanotecController : public SINQController {
|
||||
public:
|
||||
NanotecController(const char *portName, const char *NanotecPortName, int numMot, const char *busAddresses);
|
||||
|
||||
@ -50,7 +53,7 @@ friend class NanotecAxis;
|
||||
private:
|
||||
asynUser *pasynUserController_;
|
||||
|
||||
asynStatus transactController(char command[COMLEN], char reply[COMLEN]);
|
||||
asynStatus transactController(int axisNo, char command[COMLEN], char reply[COMLEN]);
|
||||
|
||||
|
||||
|
||||
|
@ -23,6 +23,12 @@ Though this driver has been written in 2016, the MCC-2 version used is probably
|
||||
Mark Koennecke
|
||||
September 2016
|
||||
|
||||
Updated to use the new MsgTxt field through SINQAxis
|
||||
Added a selector to support multiple phytrons on a connection
|
||||
|
||||
Mark Koennecke
|
||||
January 2019
|
||||
|
||||
*/
|
||||
|
||||
|
||||
@ -49,19 +55,16 @@ September 2016
|
||||
* \param[in] PhytronPortName The name of the drvAsynSerialPort that was created previously to connect to the Phytron controller
|
||||
* \param[in] numAxes The number of axes that this controller supports
|
||||
*/
|
||||
PhytronController::PhytronController(const char *portName, const char *PhytronPortName, int encX, int encY)
|
||||
: asynMotorController(portName, 3, 0,
|
||||
0, // No additional interfaces beyond those in base class
|
||||
0, // No additional callback interfaces beyond those in base class
|
||||
ASYN_CANBLOCK | ASYN_MULTIDEVICE,
|
||||
1, // autoconnect
|
||||
0, 0) // Default priority and stack size
|
||||
PhytronController::PhytronController(const char *portName, const char *PhytronPortName, const char *sel ,
|
||||
int encX, int encY)
|
||||
: SINQController(portName, PhytronPortName,2)
|
||||
{
|
||||
asynStatus status;
|
||||
PhytronAxis *pAxis;
|
||||
static const char *functionName = "PhytronController::PhytronController";
|
||||
char etx[2];
|
||||
|
||||
selector = strdup(sel);
|
||||
|
||||
/* Connect to Phytron controller */
|
||||
status = pasynOctetSyncIO->connect(PhytronPortName, 0, &pasynUserController_, NULL);
|
||||
if (status) {
|
||||
@ -74,12 +77,27 @@ PhytronController::PhytronController(const char *portName, const char *PhytronPo
|
||||
pasynOctetSyncIO->setOutputEos(pasynUserController_,etx,strlen(etx));
|
||||
pasynOctetSyncIO->setInputEos(pasynUserController_,etx,strlen(etx));
|
||||
|
||||
pAxis = new PhytronAxis(this, 1, encX);
|
||||
pAxis = new PhytronAxis(this, 2, encY);
|
||||
/* The special selector D selects the dose controlled axis version */
|
||||
if(strcmp(sel, (const char *)"D") == 0) {
|
||||
new PhytronDoseAxis(this, 1, encX);
|
||||
new PhytronDoseAxis(this, 2, encY);
|
||||
} else {
|
||||
new PhytronAxis(this, 1, encX);
|
||||
new PhytronAxis(this, 2, encY);
|
||||
}
|
||||
|
||||
|
||||
startPoller(1000./1000., IDLEPOLL, 2);
|
||||
}
|
||||
|
||||
PhytronDoseController::PhytronDoseController(const char *portName, const char *PhytronPortName, const char *sel ,
|
||||
int encX, int encY)
|
||||
: PhytronController(portName, PhytronPortName, sel, encX, encY)
|
||||
{
|
||||
new PhytronDoseAxis(this, 1, encX);
|
||||
new PhytronDoseAxis(this, 2, encY);
|
||||
}
|
||||
|
||||
|
||||
/** Creates a new PhytronController object.
|
||||
* Configuration command, called directly or from iocsh
|
||||
@ -87,14 +105,21 @@ PhytronController::PhytronController(const char *portName, const char *PhytronPo
|
||||
* \param[in] PhytronPortName The name of the drvAsynIPPPort that was created previously to connect to the Phytron controller
|
||||
* \param[in] numAxes The number of axes that this controller supports
|
||||
*/
|
||||
extern "C" int PhytronCreateController(const char *portName, const char *PhytronPortName, int encX, int encY)
|
||||
extern "C" int PhytronCreateController(const char *portName, const char *PhytronPortName, const char *selector,
|
||||
int encX, int encY)
|
||||
{
|
||||
PhytronController *pPhytronController
|
||||
= new PhytronController(portName, PhytronPortName, encX, encY);
|
||||
pPhytronController = NULL;
|
||||
new PhytronController(portName, PhytronPortName,selector, encX, encY);
|
||||
return(asynSuccess);
|
||||
}
|
||||
|
||||
extern "C" int PhytronDoseCreateController(const char *portName, const char *PhytronPortName, const char *selector,
|
||||
int encX, int encY)
|
||||
{
|
||||
new PhytronDoseController(portName, PhytronPortName,selector, encX, encY);
|
||||
return(asynSuccess);
|
||||
}
|
||||
|
||||
|
||||
/** Reports on status of the driver
|
||||
* \param[in] fp The file pointer on which report information will be written
|
||||
* \param[in] level The level of report detail desired
|
||||
@ -127,6 +152,23 @@ PhytronAxis* PhytronController::getAxis(int axisNo)
|
||||
return static_cast<PhytronAxis*>(asynMotorController::getAxis(axisNo));
|
||||
}
|
||||
|
||||
/** Returns a pointer to an PhytronDoseAxis object.
|
||||
* Returns NULL if the axis number encoded in pasynUser is invalid.
|
||||
* \param[in] pasynUser asynUser structure that encodes the axis index number. */
|
||||
PhytronDoseAxis* PhytronDoseController::getAxis(asynUser *pasynUser)
|
||||
{
|
||||
return static_cast<PhytronDoseAxis*>(asynMotorController::getAxis(pasynUser));
|
||||
}
|
||||
|
||||
/** Returns a pointer to an PhytronDoseAxis object.
|
||||
* Returns NULL if the axis number encoded in pasynUser is invalid.
|
||||
* \param[in] axisNo Axis index number. */
|
||||
PhytronDoseAxis* PhytronDoseController::getAxis(int axisNo)
|
||||
{
|
||||
return static_cast<PhytronDoseAxis*>(asynMotorController::getAxis(axisNo));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* send a command to the Phytron and read the reply. Do some error and controller
|
||||
* issue fixing on the way
|
||||
@ -134,14 +176,16 @@ PhytronAxis* PhytronController::getAxis(int axisNo)
|
||||
* \param[out] reply The controllers reply
|
||||
*/
|
||||
|
||||
asynStatus PhytronController::transactController(char command[COMLEN], char reply[COMLEN])
|
||||
asynStatus PhytronController::transactController(int axisNo,char command[COMLEN], char reply[COMLEN])
|
||||
{
|
||||
asynStatus status;
|
||||
size_t in, out;
|
||||
int reason;
|
||||
char myReply[COMLEN+10], myCommand[COMLEN+10], *pPtr;
|
||||
SINQAxis *axis = getAxis(axisNo);
|
||||
|
||||
pasynOctetSyncIO->flush(pasynUserController_);
|
||||
|
||||
/* pasynOctetSyncIO->flush(pasynUserController_); */
|
||||
|
||||
|
||||
memset(myCommand,0,sizeof(myCommand));
|
||||
@ -155,6 +199,9 @@ asynStatus PhytronController::transactController(char command[COMLEN], char repl
|
||||
status = pasynOctetSyncIO->writeRead(pasynUserController_, myCommand, strlen(myCommand),
|
||||
myReply,sizeof(myReply), 1.,&out,&in,&reason);
|
||||
if(status != asynSuccess){
|
||||
if(axis!= NULL){
|
||||
axis->updateMsgTxtFromDriver("Lost connection to motor controller");
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
@ -176,7 +223,7 @@ asynStatus PhytronController::transactController(char command[COMLEN], char repl
|
||||
I may need to replace the ETX. But I am not sure if asyn did
|
||||
not remove it for me.
|
||||
*/
|
||||
strncat(reply,pPtr,sizeof(reply));
|
||||
strncat(reply,pPtr,COMLEN-1);
|
||||
|
||||
|
||||
return status;
|
||||
@ -191,7 +238,7 @@ asynStatus PhytronController::transactController(char command[COMLEN], char repl
|
||||
* Initializes register numbers, etc.
|
||||
*/
|
||||
PhytronAxis::PhytronAxis(PhytronController *pC, int axisNo, int enc)
|
||||
: asynMotorAxis(pC, axisNo),
|
||||
: SINQAxis(pC, axisNo),
|
||||
pC_(pC)
|
||||
{
|
||||
encoder = enc;
|
||||
@ -200,10 +247,23 @@ PhytronAxis::PhytronAxis(PhytronController *pC, int axisNo, int enc)
|
||||
} else {
|
||||
phytronChar = 'Y';
|
||||
}
|
||||
|
||||
haveBrake = 0;
|
||||
brakeIO = -1;
|
||||
next_poll = -1;
|
||||
homing = 0;
|
||||
homing_direction = 0;
|
||||
}
|
||||
|
||||
|
||||
int PhytronAxis::setBrake(int brakeNO)
|
||||
{
|
||||
if(brakeNO < 1 || brakeNO > 8) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
haveBrake = 1;
|
||||
brakeIO = brakeNO;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/** Reports on status of the axis
|
||||
* \param[in] fp The file pointer on which report information will be written
|
||||
@ -229,15 +289,46 @@ asynStatus PhytronAxis::move(double position, int relative, double minVelocity,
|
||||
//static const char *functionName = "PhytronAxis::move";
|
||||
char command[COMLEN], reply[COMLEN];
|
||||
|
||||
updateMsgTxtFromDriver("");
|
||||
|
||||
/*
|
||||
deal with brake
|
||||
*/
|
||||
if(haveBrake) {
|
||||
sprintf(command,"%sA%dS", pC_->selector, brakeIO);
|
||||
status = pC_->transactController(axisNo_,command,reply);
|
||||
if(strstr(reply,"NACK") != NULL){
|
||||
errlogSevPrintf(errlogMajor, "Failed to release brake on %d", axisNo_);
|
||||
updateMsgTxtFromDriver("Failed to release brake");
|
||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
return asynError;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
set speed
|
||||
*/
|
||||
sprintf(command, "%s%cP14S%f", pC_->selector, phytronChar, maxVelocity);
|
||||
status = pC_->transactController(axisNo_,command,reply);
|
||||
if(strstr(reply,"NACK") != NULL){
|
||||
errlogSevPrintf(errlogMajor, "Speed not accepted on %d", axisNo_);
|
||||
updateMsgTxtFromDriver("Invalid speed");
|
||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
return asynError;
|
||||
}
|
||||
|
||||
/*
|
||||
actually send a move command
|
||||
*/
|
||||
if (relative) {
|
||||
position += this->position;
|
||||
}
|
||||
homing = 0;
|
||||
sprintf(command, "0%cA%f", phytronChar,position/1000.);
|
||||
status = pC_->transactController(command,reply);
|
||||
sprintf(command, "%s%cA%f", pC_->selector,phytronChar,position/1000.);
|
||||
status = pC_->transactController(axisNo_,command,reply);
|
||||
if(strstr(reply,"NACK") != NULL){
|
||||
errlogSevPrintf(errlogMajor, "Drive command not acknowledged on %d", axisNo_);
|
||||
updateMsgTxtFromDriver("Drive command not acknowledged");
|
||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
return asynError;
|
||||
}
|
||||
@ -251,13 +342,47 @@ asynStatus PhytronAxis::home(double minVelocity, double maxVelocity, double acce
|
||||
//static const char *functionName = "PhytronAxis::home";
|
||||
char command[COMLEN], reply[COMLEN];
|
||||
|
||||
sprintf(command, "0%cO-",phytronChar);
|
||||
updateMsgTxtFromDriver("");
|
||||
|
||||
/*
|
||||
handle the fucking brake
|
||||
*/
|
||||
if(haveBrake) {
|
||||
sprintf(command,"%sA%dS", pC_->selector, brakeIO);
|
||||
status = pC_->transactController(axisNo_,command,reply);
|
||||
if(strstr(reply,"NACK") != NULL){
|
||||
errlogSevPrintf(errlogMajor, "Failed to release brake on %d", axisNo_);
|
||||
updateMsgTxtFromDriver("Failed to release brake");
|
||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
return asynError;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
set speed
|
||||
*/
|
||||
sprintf(command, "%s%cP14S%f", pC_->selector, phytronChar, maxVelocity);
|
||||
status = pC_->transactController(axisNo_,command,reply);
|
||||
if(strstr(reply,"NACK") != NULL){
|
||||
errlogSevPrintf(errlogMajor, "Speed not accepted on %d", axisNo_);
|
||||
updateMsgTxtFromDriver("Invalid speed");
|
||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
return asynError;
|
||||
}
|
||||
|
||||
homing_direction = forwards;
|
||||
if(forwards){
|
||||
sprintf(command, "%s%c0+",pC_->selector,phytronChar);
|
||||
} else {
|
||||
sprintf(command, "%s%c0-",pC_->selector,phytronChar);
|
||||
}
|
||||
homing = 1;
|
||||
next_poll= -1;
|
||||
status = pC_->transactController(command,reply);
|
||||
status = pC_->transactController(axisNo_,command,reply);
|
||||
if(strstr(reply,"NACK") != NULL){
|
||||
errlogSevPrintf(errlogMajor, "Home command not acknowledged on %d", axisNo_);
|
||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
updateMsgTxtFromDriver("Home command not acknowledged");
|
||||
return asynError;
|
||||
}
|
||||
return status;
|
||||
@ -274,6 +399,7 @@ asynStatus PhytronAxis::moveVelocity(double minVelocity, double maxVelocity, dou
|
||||
// "%s: minVelocity=%f, maxVelocity=%f, acceleration=%f\n",
|
||||
// functionName, minVelocity, maxVelocity, acceleration);
|
||||
|
||||
updateMsgTxtFromDriver("");
|
||||
|
||||
|
||||
if (maxVelocity > 0.) {
|
||||
@ -294,9 +420,10 @@ asynStatus PhytronAxis::stop(double acceleration )
|
||||
//static const char *functionName = "PhytronAxis::stop";
|
||||
char command[COMLEN], reply[COMLEN];
|
||||
|
||||
sprintf(command, "0%cSN", phytronChar);
|
||||
status = pC_->transactController(command,reply);
|
||||
sprintf(command, "%s%cSN", pC_->selector,phytronChar);
|
||||
status = pC_->transactController(axisNo_,command,reply);
|
||||
errlogPrintf("Sent STOP on Axis %d\n", axisNo_);
|
||||
updateMsgTxtFromDriver("Axis interrupted");
|
||||
|
||||
return status;
|
||||
}
|
||||
@ -307,10 +434,12 @@ asynStatus PhytronAxis::setPosition(double position)
|
||||
//static const char *functionName = "PhytronAxis::setPosition";
|
||||
char command[COMLEN], reply[COMLEN];
|
||||
|
||||
sprintf(command, "0%cP22S%f", phytronChar, position/1000.);
|
||||
status = pC_->transactController(command,reply);
|
||||
sprintf(command, "0%cP20S%f", phytronChar, position/1000.);
|
||||
status = pC_->transactController(command,reply);
|
||||
errlogPrintf("PhytronAxis::setPosition called with %lf\n", position);
|
||||
|
||||
sprintf(command, "%s%cP22S%f", pC_->selector,phytronChar, position/1000.);
|
||||
status = pC_->transactController(axisNo_,command,reply);
|
||||
sprintf(command, "%s%cP20S%f", pC_->selector,phytronChar, position/1000.);
|
||||
status = pC_->transactController(axisNo_,command,reply);
|
||||
next_poll = -1;
|
||||
|
||||
return status;
|
||||
@ -336,7 +465,7 @@ asynStatus PhytronAxis::setClosedLoop(bool closedLoop)
|
||||
* \param[out] moving A flag that is set indicating that the axis is moving (true) or done (false). */
|
||||
asynStatus PhytronAxis::poll(bool *moving)
|
||||
{
|
||||
asynStatus comStatus;
|
||||
asynStatus comStatus = asynSuccess;
|
||||
char command[COMLEN], reply[COMLEN];
|
||||
double lowlim;
|
||||
|
||||
@ -347,19 +476,24 @@ asynStatus PhytronAxis::poll(bool *moving)
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
setIntegerParam(pC_->motorStatusProblem_, false);
|
||||
// Read the current motor position
|
||||
if(encoder) {
|
||||
sprintf(command,"0%cP22R",phytronChar);
|
||||
sprintf(command,"%s%cP22R",pC_->selector,phytronChar);
|
||||
} else {
|
||||
sprintf(command,"0%cP20R",phytronChar);
|
||||
sprintf(command,"%s%cP20R",pC_->selector,phytronChar);
|
||||
}
|
||||
|
||||
comStatus = pC_->transactController(command,reply);
|
||||
if(comStatus) goto skip;
|
||||
|
||||
comStatus = pC_->transactController(axisNo_,command,reply);
|
||||
if(comStatus == asynError) {
|
||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
updateMsgTxtFromDriver("No connection to phytron controller");
|
||||
goto skip;
|
||||
}
|
||||
if(strstr(reply,"NACK") != NULL){
|
||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
errlogSevPrintf(errlogMajor, "Bad reply for position on %d", axisNo_);
|
||||
updateMsgTxtFromDriver("Bad reply reading position");
|
||||
goto skip;
|
||||
}
|
||||
/*
|
||||
@ -372,9 +506,13 @@ asynStatus PhytronAxis::poll(bool *moving)
|
||||
|
||||
|
||||
// Read the moving status of this motor
|
||||
sprintf(command,"0%c=H",phytronChar);
|
||||
comStatus = pC_->transactController(command,reply);
|
||||
if(comStatus) goto skip;
|
||||
sprintf(command,"%s%c=H",pC_->selector,phytronChar);
|
||||
comStatus = pC_->transactController(axisNo_,command,reply);
|
||||
if(comStatus == asynError){
|
||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
updateMsgTxtFromDriver("No connection to phytron controller");
|
||||
goto skip;
|
||||
}
|
||||
/* errlogPrintf("Axis %d, status reply %s, position %lf\n", axisNo_, reply, position); */
|
||||
if(strstr(reply,"ACKN") != NULL){
|
||||
*moving = true;
|
||||
@ -384,23 +522,45 @@ asynStatus PhytronAxis::poll(bool *moving)
|
||||
*moving = false;
|
||||
next_poll = time(NULL)+IDLEPOLL;
|
||||
setIntegerParam(pC_->motorStatusDone_, true);
|
||||
|
||||
if(haveBrake) {
|
||||
sprintf(command,"%sA%dR", pC_->selector, brakeIO);
|
||||
comStatus = pC_->transactController(axisNo_,command,reply);
|
||||
if(strstr(reply,"NACK") != NULL){
|
||||
errlogSevPrintf(errlogMajor, "Failed to set brake on %d", axisNo_);
|
||||
updateMsgTxtFromDriver("Failed to set brake");
|
||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
return asynError;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(!*moving) {
|
||||
if(homing){
|
||||
pC_->getDoubleParam(axisNo_,pC_->motorLowLimit_,&lowlim);
|
||||
if(homing_direction) {
|
||||
pC_->getDoubleParam(axisNo_,pC_->motorHighLimit_,&lowlim);
|
||||
} else {
|
||||
pC_->getDoubleParam(axisNo_,pC_->motorLowLimit_,&lowlim);
|
||||
}
|
||||
setPosition(lowlim);
|
||||
setIntegerParam(pC_->motorStatusAtHome_, true);
|
||||
} else {
|
||||
/*
|
||||
check limits and errors, upper
|
||||
*/
|
||||
sprintf(command,"0%c=I+",phytronChar);
|
||||
comStatus = pC_->transactController(command,reply);
|
||||
if(comStatus) goto skip;
|
||||
sprintf(command,"%s%c=I+",pC_->selector,phytronChar);
|
||||
comStatus = pC_->transactController(axisNo_,command,reply);
|
||||
if(comStatus == asynError) {
|
||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
updateMsgTxtFromDriver("No connection to phytron controller");
|
||||
goto skip;
|
||||
}
|
||||
if(strstr(reply,"ACKE") != NULL){
|
||||
setIntegerParam(pC_->motorStatusHighLimit_, true);
|
||||
updateMsgTxtFromDriver("Hit High Limit");
|
||||
comStatus = asynError;
|
||||
goto skip;
|
||||
} else {
|
||||
setIntegerParam(pC_->motorStatusHighLimit_, false);
|
||||
}
|
||||
@ -408,11 +568,18 @@ asynStatus PhytronAxis::poll(bool *moving)
|
||||
/*
|
||||
lower limit
|
||||
*/
|
||||
sprintf(command,"0%c=I-",phytronChar);
|
||||
comStatus = pC_->transactController(command,reply);
|
||||
if(comStatus) goto skip;
|
||||
sprintf(command,"%s%c=I-",pC_->selector,phytronChar);
|
||||
comStatus = pC_->transactController(axisNo_,command,reply);
|
||||
if(comStatus == asynError){
|
||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
updateMsgTxtFromDriver("No connection to phytron controller");
|
||||
goto skip;
|
||||
}
|
||||
if(strstr(reply,"ACKE") != NULL){
|
||||
setIntegerParam(pC_->motorStatusLowLimit_, true);
|
||||
updateMsgTxtFromDriver("Low Limit Hit");
|
||||
comStatus = asynError;
|
||||
goto skip;
|
||||
} else {
|
||||
setIntegerParam(pC_->motorStatusLowLimit_, false);
|
||||
}
|
||||
@ -420,12 +587,19 @@ asynStatus PhytronAxis::poll(bool *moving)
|
||||
/*
|
||||
error
|
||||
*/
|
||||
sprintf(command,"0%c=E",phytronChar);
|
||||
comStatus = pC_->transactController(command,reply);
|
||||
if(comStatus) goto skip;
|
||||
sprintf(command,"%s%c=E",pC_->selector,phytronChar);
|
||||
comStatus = pC_->transactController(axisNo_,command,reply);
|
||||
if(comStatus == asynError) {
|
||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
updateMsgTxtFromDriver("No connection to phytron controller");
|
||||
goto skip;
|
||||
}
|
||||
if(strstr(reply,"ACKE") != NULL){
|
||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
errlogSevPrintf(errlogMajor, "Electronics on %d", axisNo_);
|
||||
updateMsgTxtFromDriver("Electronics error");
|
||||
comStatus = asynError;
|
||||
goto skip;
|
||||
} else {
|
||||
setIntegerParam(pC_->motorStatusProblem_, false);
|
||||
}
|
||||
@ -434,29 +608,165 @@ asynStatus PhytronAxis::poll(bool *moving)
|
||||
|
||||
|
||||
skip:
|
||||
setIntegerParam(pC_->motorStatusProblem_, comStatus ? 1:0);
|
||||
callParamCallbacks();
|
||||
return comStatus ? asynError : asynSuccess;
|
||||
return comStatus;
|
||||
}
|
||||
|
||||
/* The special PhytronDoseAxis code used when the speed is controlled through the neutron flux
|
||||
*/
|
||||
|
||||
PhytronDoseAxis::PhytronDoseAxis(PhytronController *pC, int axisNo, int enc)
|
||||
: PhytronAxis(pC, axisNo, enc)
|
||||
{
|
||||
if(axisNo == 1){
|
||||
doseChar = '3';
|
||||
} else {
|
||||
doseChar = '4';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
asynStatus PhytronDoseAxis::move(double position, int relative, double minVelocity, double maxVelocity, double acceleration)
|
||||
{
|
||||
asynStatus status;
|
||||
//static const char *functionName = "PhytronDoseAxis::move";
|
||||
char command[COMLEN], reply[COMLEN];
|
||||
float realTarget;
|
||||
|
||||
updateMsgTxtFromDriver("");
|
||||
|
||||
/*
|
||||
set conversion factor from neutron rate to speed
|
||||
*/
|
||||
sprintf(command, "R%c3S%f", doseChar, maxVelocity);
|
||||
status = pC_->transactController(axisNo_,command,reply);
|
||||
if(strstr(reply,"NACK") != NULL){
|
||||
errlogSevPrintf(errlogMajor, "Speed not accepted on %d", axisNo_);
|
||||
updateMsgTxtFromDriver("Invalid speed");
|
||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
return asynError;
|
||||
}
|
||||
|
||||
/*
|
||||
actually send a move command
|
||||
*/
|
||||
if (relative) {
|
||||
position += this->position;
|
||||
}
|
||||
homing = 0;
|
||||
|
||||
/* set target */
|
||||
realTarget = position/1000.;
|
||||
if(realTarget > this->position) {
|
||||
sprintf(command, "R%c1S%f", doseChar,realTarget);
|
||||
} else {
|
||||
sprintf(command, "R%c2S%f", doseChar,realTarget);
|
||||
}
|
||||
status = pC_->transactController(axisNo_,command,reply);
|
||||
if(strstr(reply,"NACK") != NULL){
|
||||
errlogSevPrintf(errlogMajor, "Drive command not acknowledged on %d", axisNo_);
|
||||
updateMsgTxtFromDriver("Drive command not acknowledged");
|
||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
return asynError;
|
||||
}
|
||||
|
||||
/* really start the move */
|
||||
sprintf(command, "R%c01S", doseChar);
|
||||
status = pC_->transactController(axisNo_,command,reply);
|
||||
if(strstr(reply,"NACK") != NULL){
|
||||
errlogSevPrintf(errlogMajor, "Drive start command not acknowledged on %d", axisNo_);
|
||||
updateMsgTxtFromDriver("Drive command not acknowledged");
|
||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
return asynError;
|
||||
}
|
||||
|
||||
next_poll = -1;
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
extern "C" asynStatus PhytronConfBrake(const char *port, /* specify which controller by port name */
|
||||
int axis, /* axis: 0, 1 */
|
||||
int brakeNO) /* brakeIO No, 1-8 */
|
||||
{
|
||||
PhytronController *pC;
|
||||
PhytronAxis *pAxis;
|
||||
int status;
|
||||
|
||||
static const char *functionName = "PhytronConfBrake";
|
||||
|
||||
pC = (PhytronController*) findAsynPortDriver(port);
|
||||
if (!pC) {
|
||||
printf("%s:%s: Error port %s not found\n",
|
||||
"PhytronDriver", functionName, port);
|
||||
return asynError;
|
||||
}
|
||||
|
||||
pC->lock();
|
||||
pAxis = pC->getAxis(axis);
|
||||
status = pAxis->setBrake(brakeNO);
|
||||
pC->unlock();
|
||||
if(!status) {
|
||||
printf("%s:%s:%s requested brake IO out of range\n",
|
||||
"PhytronDriver", functionName, port);
|
||||
return asynError;
|
||||
}
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
/** Code for iocsh registration */
|
||||
static const iocshArg PhytronCreateControllerArg0 = {"Port name", iocshArgString};
|
||||
static const iocshArg PhytronCreateControllerArg1 = {"Phytron port name", iocshArgString};
|
||||
static const iocshArg PhytronCreateControllerArg2 = {"EnoderX", iocshArgInt};
|
||||
static const iocshArg PhytronCreateControllerArg3 = {"EnoderY", iocshArgInt};
|
||||
static const iocshArg PhytronCreateControllerArg2 = {"Phytron Selector", iocshArgString};
|
||||
static const iocshArg PhytronCreateControllerArg3 = {"EncoderX", iocshArgInt};
|
||||
static const iocshArg PhytronCreateControllerArg4 = {"EncoderY", iocshArgInt};
|
||||
static const iocshArg * const PhytronCreateControllerArgs[] = {&PhytronCreateControllerArg0,
|
||||
&PhytronCreateControllerArg1,
|
||||
&PhytronCreateControllerArg2,
|
||||
&PhytronCreateControllerArg3};
|
||||
static const iocshFuncDef PhytronCreateControllerDef = {"PhytronCreateController", 4, PhytronCreateControllerArgs};
|
||||
&PhytronCreateControllerArg3,
|
||||
&PhytronCreateControllerArg4};
|
||||
static const iocshFuncDef PhytronCreateControllerDef = {"PhytronCreateController", 5, PhytronCreateControllerArgs};
|
||||
static void PhytronCreateContollerCallFunc(const iocshArgBuf *args)
|
||||
{
|
||||
PhytronCreateController(args[0].sval, args[1].sval, args[2].ival,args[3].ival);
|
||||
PhytronCreateController(args[0].sval, args[1].sval, args[2].sval, args[3].ival,args[4].ival);
|
||||
}
|
||||
|
||||
static const iocshArg PhytronDoseCreateControllerArg0 = {"Port name", iocshArgString};
|
||||
static const iocshArg PhytronDoseCreateControllerArg1 = {"Phytron port name", iocshArgString};
|
||||
static const iocshArg PhytronDoseCreateControllerArg2 = {"Phytron Selector", iocshArgString};
|
||||
static const iocshArg PhytronDoseCreateControllerArg3 = {"EncoderX", iocshArgInt};
|
||||
static const iocshArg PhytronDoseCreateControllerArg4 = {"EncoderY", iocshArgInt};
|
||||
static const iocshArg * const PhytronDoseCreateControllerArgs[] = {&PhytronDoseCreateControllerArg0,
|
||||
&PhytronDoseCreateControllerArg1,
|
||||
&PhytronDoseCreateControllerArg2,
|
||||
&PhytronDoseCreateControllerArg3,
|
||||
&PhytronDoseCreateControllerArg4};
|
||||
static const iocshFuncDef PhytronDoseCreateControllerDef = {"PhytronDoseCreateController", 5, PhytronDoseCreateControllerArgs};
|
||||
static void PhytronDoseCreateContollerCallFunc(const iocshArgBuf *args)
|
||||
{
|
||||
PhytronDoseCreateController(args[0].sval, args[1].sval, args[2].sval, args[3].ival,args[4].ival);
|
||||
}
|
||||
|
||||
/* PhytronConfigureBrake */
|
||||
static const iocshArg phytronBrakeArg0 = {"Controller port name", iocshArgString};
|
||||
static const iocshArg phytronBrakeArg1 = {"Axis number", iocshArgInt};
|
||||
static const iocshArg phytronBrakeArg2 = {"Brake IO number", iocshArgInt};
|
||||
static const iocshArg * const phytronBrakeArgs[] = {&phytronBrakeArg0,
|
||||
&phytronBrakeArg1,
|
||||
&phytronBrakeArg2};
|
||||
static const iocshFuncDef phytronBrakeDef = {"PhytronConfigureBrake", 3, phytronBrakeArgs};
|
||||
|
||||
static void phytronBrakeCallFunc(const iocshArgBuf *args)
|
||||
{
|
||||
PhytronConfBrake(args[0].sval, args[1].ival, args[2].ival);
|
||||
}
|
||||
|
||||
|
||||
static void PhytronRegister(void)
|
||||
{
|
||||
iocshRegister(&PhytronCreateControllerDef, PhytronCreateContollerCallFunc);
|
||||
iocshRegister(&PhytronDoseCreateControllerDef, PhytronDoseCreateContollerCallFunc);
|
||||
iocshRegister(&phytronBrakeDef, phytronBrakeCallFunc);
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
@ -5,14 +5,28 @@ USAGE... Motor driver support for the Phytron MCC-2 motor controller.
|
||||
Mark Koennecke
|
||||
September 2016
|
||||
|
||||
|
||||
Updated to go through SINQAxis for -MsgTxt support
|
||||
Added a selector to support multiple phytrons on a connection
|
||||
|
||||
Mark Koennecke, January 2019
|
||||
|
||||
Added PhytronDoseAxis. The speed of this axis is controlled by the
|
||||
neutron dose rate fed in as analog signal
|
||||
|
||||
Mark Koennecke, April 2023
|
||||
*/
|
||||
|
||||
#include "asynMotorController.h"
|
||||
#include "asynMotorAxis.h"
|
||||
#include "SINQController.h"
|
||||
#include "SINQAxis.h"
|
||||
|
||||
#define COMLEN 80
|
||||
|
||||
class PhytronAxis : public asynMotorAxis
|
||||
|
||||
class PhytronController;
|
||||
class PhytronDoseController;
|
||||
|
||||
class PhytronAxis : public SINQAxis
|
||||
{
|
||||
public:
|
||||
/* These are the methods we override from the base class */
|
||||
@ -25,32 +39,71 @@ public:
|
||||
asynStatus poll(bool *moving);
|
||||
asynStatus setPosition(double position);
|
||||
asynStatus setClosedLoop(bool closedLoop);
|
||||
int setBrake(int brakeIO);
|
||||
|
||||
private:
|
||||
protected:
|
||||
char phytronChar;
|
||||
PhytronController *pC_; /**< Pointer to the asynMotorController to which this axis belongs.
|
||||
* Abbreviated because it is used very frequently */
|
||||
double position;
|
||||
int homing;
|
||||
int homing_direction; /*1 forward, 0 backwards */
|
||||
time_t next_poll;
|
||||
int encoder;
|
||||
int haveBrake;
|
||||
int brakeIO;
|
||||
|
||||
friend class PhytronController;
|
||||
};
|
||||
|
||||
class PhytronController : public asynMotorController {
|
||||
class PhytronDoseAxis : public PhytronAxis
|
||||
{
|
||||
// A special version of PhytronAxis where the speed is controlled by the neutron flux.
|
||||
public:
|
||||
PhytronController(const char *portName, const char *PhytronPortName, int encX, int encY);
|
||||
PhytronDoseAxis(class PhytronController *pC, int axis, int enc);
|
||||
asynStatus move(double position, int relative, double min_velocity, double max_velocity, double acceleration);
|
||||
|
||||
friend class PhytronDoseController;
|
||||
|
||||
protected:
|
||||
char doseChar;
|
||||
PhytronDoseController *pC_;
|
||||
};
|
||||
|
||||
|
||||
class PhytronController : public SINQController {
|
||||
public:
|
||||
PhytronController(const char *portName, const char *PhytronPortName, const char *selector,
|
||||
int encX, int encY);
|
||||
|
||||
void report(FILE *fp, int level);
|
||||
PhytronAxis* getAxis(asynUser *pasynUser);
|
||||
PhytronAxis* getAxis(int axisNo);
|
||||
|
||||
friend class PhytronAxis;
|
||||
private:
|
||||
|
||||
protected:
|
||||
asynUser *pasynUserController_;
|
||||
|
||||
asynStatus transactController(char command[COMLEN], char reply[COMLEN]);
|
||||
asynStatus transactController(int axisNo, char command[COMLEN], char reply[COMLEN]);
|
||||
|
||||
const char *selector;
|
||||
|
||||
};
|
||||
|
||||
class PhytronDoseController : public PhytronController {
|
||||
public:
|
||||
PhytronDoseController(const char *portName, const char *PhytronPortName, const char *selector,
|
||||
int encX, int encY);
|
||||
|
||||
PhytronDoseAxis* getAxis(asynUser *pasynUser);
|
||||
PhytronDoseAxis* getAxis(int axisNo);
|
||||
|
||||
friend class PhytronDoseAxis;
|
||||
|
||||
protected:
|
||||
asynUser *pasynUserController_;
|
||||
const char *selector;
|
||||
|
||||
};
|
||||
|
||||
|
@ -12,6 +12,7 @@ SINQAxis::SINQAxis(class SINQController *pC, int axis)
|
||||
: asynMotorAxis((asynMotorController *)pC, axis),
|
||||
pC_(pC)
|
||||
{
|
||||
updateMsgTxtFromDriver("");
|
||||
}
|
||||
|
||||
|
||||
@ -22,6 +23,7 @@ void SINQAxis::updateMsgTxtFromDriver(const char *value)
|
||||
setStringParam(pC_->motorMessageText_,value);
|
||||
} else {
|
||||
pC_->setIntegerParam(axisNo_,pC_->motorMessageIsFromDriver_, 0);
|
||||
setStringParam(pC_->motorMessageText_,"");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,13 +5,19 @@
|
||||
Code lifted from Torsten Boegershausen ESS code.
|
||||
|
||||
Mark Koennecke, March 2017
|
||||
|
||||
Added code to manage an interMessageSleep
|
||||
|
||||
Mark Koennecke, February 2024
|
||||
*/
|
||||
|
||||
#include "SINQController.h"
|
||||
#include "asynMotorController.h"
|
||||
#include "epicsExport.h"
|
||||
#include "iocsh.h"
|
||||
|
||||
SINQController::SINQController(const char *portName, const char *SINQPortName, int numAxes)
|
||||
: asynMotorController(portName, numAxes+1, NUM_MOTOR_DRIVER_PARAMS+2,
|
||||
SINQController::SINQController(const char *portName, const char *SINQPortName, int numAxes, const int& extraParams)
|
||||
: asynMotorController(portName, numAxes+1, NUM_MOTOR_DRIVER_PARAMS+extraParams,
|
||||
0, // No additional interfaces beyond those in base class
|
||||
0, // No additional callback interfaces beyond those in base class
|
||||
ASYN_CANBLOCK | ASYN_MULTIDEVICE,
|
||||
@ -20,6 +26,53 @@ SINQController::SINQController(const char *portName, const char *SINQPortName, i
|
||||
{
|
||||
createParam(motorMessageIsFromDriverString, asynParamInt32, &motorMessageIsFromDriver_);
|
||||
createParam(motorMessageTextString, asynParamOctet, &motorMessageText_);
|
||||
|
||||
interMessageSleep=20;
|
||||
}
|
||||
|
||||
/** Set the interMessageSleep at runtime */
|
||||
asynStatus SINQController::setInterMessageSleep(int messageSleep)
|
||||
{
|
||||
lock();
|
||||
interMessageSleep = messageSleep;
|
||||
unlock();
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
asynStatus setInterMessageSleep(const char *portName, int messageSleep)
|
||||
{
|
||||
SINQController *pC;
|
||||
static const char *functionName = "setIntermessageSleep";
|
||||
|
||||
pC = (SINQController*) findAsynPortDriver(portName);
|
||||
if (!pC) {
|
||||
printf("%s:%s: Error port %s not found\n", "SINQController", functionName, portName);
|
||||
return asynError;
|
||||
}
|
||||
|
||||
return pC->setInterMessageSleep(messageSleep);
|
||||
}
|
||||
|
||||
|
||||
/* setInterMessageSleep */
|
||||
static const iocshArg setInterMessageSleepArg0 = {"Controller port name", iocshArgString};
|
||||
static const iocshArg setInterMessageSleepArg1 = {"inter message sleep time", iocshArgInt};
|
||||
static const iocshArg * const setInterMessageSleepArgs[] = {&setInterMessageSleepArg0,
|
||||
&setInterMessageSleepArg1};
|
||||
static const iocshFuncDef setInterMessageSleepDef = {"setInterMessageSleep", 2, setInterMessageSleepArgs};
|
||||
|
||||
static void setInterMessageSleepCallFunc(const iocshArgBuf *args)
|
||||
{
|
||||
setInterMessageSleep(args[0].sval, args[1].ival);
|
||||
}
|
||||
|
||||
static void SINQControllerRegister(void)
|
||||
{
|
||||
iocshRegister(&setInterMessageSleepDef, setInterMessageSleepCallFunc);
|
||||
}
|
||||
epicsExportRegistrar(SINQControllerRegister);
|
||||
|
||||
} // extern C
|
||||
|
||||
|
@ -17,12 +17,16 @@
|
||||
class epicsShareClass SINQController : public asynMotorController
|
||||
{
|
||||
public:
|
||||
SINQController(const char *portName, const char *SINQPortName, int numAxes);
|
||||
|
||||
SINQController(const char *portName, const char *SINQPortName, int numAxes, const int& extraParams=2);
|
||||
asynStatus setInterMessageSleep(int messageSleep);
|
||||
|
||||
friend class SINQAxis;
|
||||
protected:
|
||||
int motorMessageIsFromDriver_;
|
||||
int motorMessageText_;
|
||||
|
||||
int interMessageSleep; // minimum time between message to the controller in microseconds
|
||||
// for slowing down communication to weak controllers.....
|
||||
};
|
||||
|
||||
|
||||
|
@ -27,7 +27,7 @@
|
||||
*
|
||||
* Mark Koennecke, February 2013
|
||||
*
|
||||
* Enhanced by adding an addtional external pause and status flag in the databse and code in here
|
||||
* Enhanced by adding an addtional external pause and status flag in the database and code in here
|
||||
* to handle this. Moreover an external MsgTxt field in the DB can be filled with an error message.
|
||||
*
|
||||
* Mark Koennecke, July 2017
|
||||
@ -40,6 +40,8 @@
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <asynOctetSyncIO.h>
|
||||
#include <epicsEvent.h>
|
||||
#include <epicsThread.h>
|
||||
@ -118,7 +120,7 @@ static void dummyAsynCallback(asynUser *pasynUser)
|
||||
|
||||
static void connectSlaveRecords(EL737priv *priv)
|
||||
{
|
||||
char slaveName[PVNAME_SZ], errName[256];
|
||||
char slaveName[PVNAME_SZ + 18], errName[256];
|
||||
long status;
|
||||
|
||||
priv->dbInit = 1;
|
||||
@ -163,10 +165,10 @@ static void connectSlaveRecords(EL737priv *priv)
|
||||
errlogPrintf("dbNameToAddr failed for %s with %s\n", slaveName, errName);
|
||||
priv->dbInit = 0;
|
||||
} else {
|
||||
errlogPrintf("dbNameToAddr succeded for %s, record access %s\n",slaveName, priv->status.precord->name);
|
||||
errlogPrintf("dbNameToAddr succeded for %s, record access %s\n",slaveName, priv->threshCounter.precord->name);
|
||||
}
|
||||
|
||||
snprintf(slaveName,sizeof(slaveName),"%s:Threshold", priv->psr->name);
|
||||
snprintf(slaveName, sizeof(slaveName), "%s:Threshold", priv->psr->name);
|
||||
errlogPrintf("Name of thresholdCounter variable: %s\n", slaveName);
|
||||
status = dbNameToAddr(slaveName,&priv->threshold);
|
||||
if(status!= 0){
|
||||
@ -174,9 +176,9 @@ static void connectSlaveRecords(EL737priv *priv)
|
||||
errlogPrintf("dbNameToAddr failed for %s with %s\n", slaveName, errName);
|
||||
priv->dbInit = 0;
|
||||
} else {
|
||||
errlogPrintf("dbNameToAddr succeded for %s, record access %s\n",slaveName, priv->status.precord->name);
|
||||
errlogPrintf("dbNameToAddr succeded for %s, record access %s\n",slaveName, priv->threshold.precord->name);
|
||||
}
|
||||
|
||||
priv->thresholdValue = -999;
|
||||
}
|
||||
|
||||
static long el737_init_record(scalerRecord *psr, CALLBACK *pcallback)
|
||||
@ -222,17 +224,18 @@ static long el737_init_record(scalerRecord *psr, CALLBACK *pcallback)
|
||||
psr->pact = 1;
|
||||
return 0;
|
||||
}
|
||||
pasynManager->autoConnect(dummyUser, 1);
|
||||
status = pasynOctetSyncIO->connect(port, 0, &priv->asynContext, NULL);
|
||||
if (status) {
|
||||
asynPrint(dummyUser, ASYN_TRACE_ERROR,
|
||||
"%s: cannot connect to EL734 controller\n",
|
||||
"%s: cannot connect to EL737 controller\n",
|
||||
"el737_init_scaler");
|
||||
psr->pact = 1;
|
||||
return 0;
|
||||
}
|
||||
pasynOctetSyncIO->setOutputEos(priv->asynContext,"\r",strlen("\r"));
|
||||
pasynOctetSyncIO->setInputEos(priv->asynContext,"\r",strlen("\r"));
|
||||
priv->asynContext->timeout = 1.0;
|
||||
priv->asynContext->timeout = 2.0;
|
||||
strcpy(command,"RMT 1");
|
||||
pasynOctetSyncIO->writeRead(priv->asynContext, command, strlen(command),
|
||||
reply,sizeof(reply), 1.,&out,&in,&reason);
|
||||
@ -287,7 +290,7 @@ static long el737_write_preset(scalerRecord *psr, int signal, unsigned long val)
|
||||
{
|
||||
EL737priv *priv;
|
||||
|
||||
//errlogPrintf("Setting preset %d to %ld\n", signal, val);
|
||||
// errlogPrintf("EL737: Setting preset %d to %ld\n", signal, val);
|
||||
|
||||
priv = (EL737priv *)psr->dpvt;
|
||||
if(signal >= 0 && signal < 13){
|
||||
@ -296,7 +299,7 @@ static long el737_write_preset(scalerRecord *psr, int signal, unsigned long val)
|
||||
if(signal == THRESHVAL){
|
||||
priv->sendThreshold = 1;
|
||||
epicsEventSignal(priv->wakeUp);
|
||||
//errlogPrintf("Setting threshold \n");
|
||||
// errlogPrintf("EL737: Setting threshold \n");
|
||||
|
||||
}
|
||||
return 0;
|
||||
@ -309,7 +312,7 @@ static long el737_arm(scalerRecord *psr, int val)
|
||||
priv = (EL737priv *)psr->dpvt;
|
||||
priv->countCommand = val;
|
||||
|
||||
//errlogPrintf("Sending arm %d \n", val);
|
||||
// errlogPrintf("EL737: Sending arm %d \n", val);
|
||||
|
||||
epicsEventSignal(priv->wakeUp);
|
||||
|
||||
@ -321,7 +324,7 @@ static long el737_done(scalerRecord *psr)
|
||||
EL737priv *priv;
|
||||
priv = (EL737priv *)psr->dpvt;
|
||||
|
||||
// errlogPrintf("Calling done with %d\n", psr->cnt);
|
||||
// errlogPrintf("EL737: Calling done with %d\n", psr->cnt);
|
||||
if(priv->counting && psr->cnt == 0){
|
||||
priv->countCommand = 0;
|
||||
epicsEventSignal(priv->wakeUp);
|
||||
@ -343,7 +346,9 @@ static asynStatus el737_transactCommand(EL737priv *priv,char command[COMLEN],cha
|
||||
|
||||
pasynOctetSyncIO->flush(priv->asynContext);
|
||||
|
||||
strcpy(message,"OK");
|
||||
// errlogPrintf("EL737: sending command %s\n", command);
|
||||
|
||||
strcpy(message,"");
|
||||
status = pasynOctetSyncIO->writeRead(priv->asynContext, command, strlen(command),
|
||||
reply,COMLEN, 1.,&out,&in,&reason);
|
||||
if(status == asynSuccess){
|
||||
@ -384,7 +389,13 @@ static asynStatus el737_transactCommand(EL737priv *priv,char command[COMLEN],cha
|
||||
}
|
||||
}
|
||||
} else {
|
||||
strncpy(message,"Lost communication",sizeof(message));
|
||||
if(errno == EAGAIN){
|
||||
errlogPrintf("Lost response to %s with EAGAIN\n", command);
|
||||
} else {
|
||||
snprintf(message,sizeof(message), "Lost communication with errno %d", errno);
|
||||
/* force a reconnect */
|
||||
pasynOctetSyncIO->disconnect(priv->asynContext);
|
||||
}
|
||||
}
|
||||
if(priv->dbInit){
|
||||
dbStatus = dbPutField(&priv->msgTxt, DBR_STRING,message, 1);
|
||||
@ -399,7 +410,7 @@ static asynStatus el737_transactCommand(EL737priv *priv,char command[COMLEN],cha
|
||||
static asynStatus sendStop(EL737priv *priv)
|
||||
{
|
||||
char command[COMLEN], reply[COMLEN];
|
||||
//errlogPrintf("Sending stop\n");
|
||||
// errlogPrintf("EL737: Sending stop\n");
|
||||
strcpy(command,"S");
|
||||
return el737_transactCommand(priv,command,reply);
|
||||
}
|
||||
@ -408,13 +419,14 @@ static void runEvents(EL737priv *priv)
|
||||
{
|
||||
char command[COMLEN], reply[COMLEN], errName[256];
|
||||
int status;
|
||||
long dbStatus, myThreshold, nElements = 1, options = 0, threshCounter;
|
||||
long dbStatus, nElements = 1, options = 0, threshCounter;
|
||||
long unsigned int myThreshold;
|
||||
|
||||
/*
|
||||
This is the better way to set the threshold rather then the old way below which uses
|
||||
presets for that function. This one uses separate fields.
|
||||
*/
|
||||
if(priv->dbInit == 1) {
|
||||
if(priv->dbInit == 1 && priv->sendThreshold) {
|
||||
dbStatus = dbGetField(&priv->threshold,DBR_LONG,&myThreshold,&options, &nElements,NULL);
|
||||
if(dbStatus != 0){
|
||||
errSymLookup(dbStatus,errName,sizeof(errName));
|
||||
@ -425,13 +437,16 @@ static void runEvents(EL737priv *priv)
|
||||
we have to set the threshold
|
||||
*/
|
||||
dbStatus = dbGetField(&priv->threshCounter,DBR_LONG,&threshCounter, &options, &nElements,NULL);
|
||||
if(dbStatus != 0){
|
||||
if(dbStatus != 0) {
|
||||
errSymLookup(dbStatus,errName,sizeof(errName));
|
||||
errlogPrintf("Reading thresholdCounter failed with %s\n", errName);
|
||||
} else {
|
||||
if(threshCounter == 0){
|
||||
threshCounter = 1;
|
||||
}
|
||||
sprintf(command,"DL %d %d", (int)threshCounter,
|
||||
(int)myThreshold);
|
||||
//errlogPrintf("Sending threshold command %s\n", command);
|
||||
// errlogPrintf("Sending threshold command %s\n", command);
|
||||
status = el737_transactCommand(priv,command,reply);
|
||||
sprintf(command,"DR %d", (int)threshCounter);
|
||||
status = el737_transactCommand(priv, command,reply);
|
||||
@ -442,35 +457,36 @@ static void runEvents(EL737priv *priv)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(priv->sendThreshold == 1){
|
||||
sprintf(command,"DL %d %d", (int)priv->presets[THRESHMON],
|
||||
(int)priv->presets[THRESHVAL]);
|
||||
//errlogPrintf("Sending threshold command %s\n", command);
|
||||
status = el737_transactCommand(priv,command,reply);
|
||||
sprintf(command,"DR %d", (int)priv->presets[THRESHMON]);
|
||||
status = el737_transactCommand(priv, command,reply);
|
||||
if(status == asynSuccess){
|
||||
priv->sendThreshold = 0;
|
||||
if(priv->sendThreshold == 1) {
|
||||
// fallback when we do not have the DB fields or threshCounter is invalid
|
||||
if(priv->presets[THRESHMON] > 0) {
|
||||
sprintf(command,"DL %d %d", (int)priv->presets[THRESHMON],
|
||||
(int)priv->presets[THRESHVAL]);
|
||||
// errlogPrintf("Sending threshold from presets, command %s\n", command);
|
||||
status = el737_transactCommand(priv,command,reply);
|
||||
sprintf(command,"DR %d", (int)priv->presets[THRESHMON]);
|
||||
status = el737_transactCommand(priv, command,reply);
|
||||
if(status == asynSuccess) {
|
||||
priv->sendThreshold = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//errlogPrintf("Doing a count command: %d\n", priv->countCommand);
|
||||
errlogPrintf("EL737: Doing a count command: %d\n", priv->countCommand);
|
||||
if(priv->countCommand == 1){
|
||||
if(priv->presets[MODE] > 0) {
|
||||
/* preset monitor */
|
||||
sprintf(command,"MP %d", (int)priv->presets[MONITOR]);
|
||||
//errlogPrintf("Starting preset monitor\n");
|
||||
// errlogPrintf("EL737: Starting preset monitor\n");
|
||||
} else {
|
||||
/* preset time */
|
||||
sprintf(command,"TP %f", priv->presets[TIMER]/1000.);
|
||||
//errlogPrintf("Starting preset timer\n");
|
||||
// errlogPrintf("EL737: Starting preset timer\n");
|
||||
}
|
||||
status = el737_transactCommand(priv,command,reply);
|
||||
if(status == asynSuccess){
|
||||
priv->counting = 1;
|
||||
}
|
||||
priv->counting = 1;
|
||||
} else {
|
||||
if(priv->counting) {
|
||||
/* Stop */
|
||||
@ -482,8 +498,8 @@ static void runEvents(EL737priv *priv)
|
||||
|
||||
|
||||
if(status != asynSuccess){
|
||||
errlogPrintf("devScalerEL737::el737Thread communication problem %s\n",
|
||||
priv->asynContext->errorMessage);
|
||||
errlogPrintf("devScalerEL737::el737Thread communication problem %s\n, errno =%d",
|
||||
priv->asynContext->errorMessage, errno);
|
||||
}
|
||||
}
|
||||
|
||||
@ -497,8 +513,12 @@ static void updateValues(EL737priv *priv)
|
||||
strcpy(command,"RA");
|
||||
status = el737_transactCommand(priv,command,reply);
|
||||
if(status != asynSuccess){
|
||||
errlogPrintf("devScalerEL737::el737Thread communication problem %s\n",
|
||||
priv->asynContext->errorMessage);
|
||||
if(errno == EAGAIN){
|
||||
errlogPrintf("devScalerEL737::el737Thread lost response to %s with EAGAIN\n", command);
|
||||
} else {
|
||||
errlogPrintf("devScalerEL737::el737Thread communication problem %s, errno = %d\n",
|
||||
priv->asynContext->errorMessage, errno);
|
||||
}
|
||||
return;
|
||||
}
|
||||
status = sscanf(reply, "%f %ld %ld %ld %ld %ld %ld %ld %ld",
|
||||
@ -536,12 +556,11 @@ static void el737Thread(void *param)
|
||||
int rs, ctStatus;
|
||||
long dbStatus, options, nElements = 1, pauseFlag = 0;
|
||||
|
||||
//errlogPrintf("Within EL737 thread \n");
|
||||
// errlogPrintf("EL737: Within EL737 thread \n");
|
||||
|
||||
while(1){
|
||||
evStatus = epicsEventWaitWithTimeout(priv->wakeUp,timeout);
|
||||
//errlogPrintf("EL737 thread woke up with %d\n", evStatus);
|
||||
|
||||
// errlogPrintf("EL737 thread woke up with %d\n", evStatus);
|
||||
|
||||
if(evStatus == epicsEventWaitOK) {
|
||||
/*
|
||||
@ -549,7 +568,8 @@ static void el737Thread(void *param)
|
||||
*/
|
||||
runEvents(priv);
|
||||
if(priv->counting == 1){
|
||||
timeout = .1;
|
||||
timeout = .2;
|
||||
usleep(500);
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
@ -568,7 +588,7 @@ static void el737Thread(void *param)
|
||||
if(status != asynSuccess){
|
||||
errlogPrintf("devScalerEL737::el737Thread communication problem %s\n",
|
||||
priv->asynContext->errorMessage);
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
sscanf(reply,"%d",&rs);
|
||||
//errlogPrintf("RS = %d\n", rs);
|
||||
@ -617,7 +637,7 @@ static void el737Thread(void *param)
|
||||
if(status != asynSuccess){
|
||||
errlogPrintf("devScalerEL737::el737Thread communication problem %s\n",
|
||||
priv->asynContext->errorMessage);
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
} else if(pauseFlag == 0 && ctStatus == 3){
|
||||
strcpy(command,"CO");
|
||||
@ -625,7 +645,7 @@ static void el737Thread(void *param)
|
||||
if(status != asynSuccess){
|
||||
errlogPrintf("devScalerEL737::el737Thread communication problem %s\n",
|
||||
priv->asynContext->errorMessage);
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,61 +1,222 @@
|
||||
/********************************************
|
||||
* pmacAxis.cpp
|
||||
*
|
||||
* PMAC Asyn motor based on the
|
||||
*
|
||||
* PMAC Asyn motor based on the
|
||||
* asynMotorAxis class.
|
||||
*
|
||||
*
|
||||
* Matthew Pearson
|
||||
* 23 May 2012
|
||||
*
|
||||
*
|
||||
* Modified to use the MsgTxt field for SINQ
|
||||
*
|
||||
* Mark Koennecke, January 2019
|
||||
*
|
||||
* EXtended with special motor axis for the Selene
|
||||
* guide, Mark Koennecke, February 2020
|
||||
********************************************/
|
||||
|
||||
#ifndef pmacAxis_H
|
||||
#define pmacAxis_H
|
||||
|
||||
#include "asynMotorController.h"
|
||||
#include "asynMotorAxis.h"
|
||||
#include "SINQAxis.h"
|
||||
#include "SINQController.h"
|
||||
|
||||
class pmacController;
|
||||
class SeleneController;
|
||||
|
||||
class pmacAxis : public asynMotorAxis
|
||||
{
|
||||
class pmacAxis : public SINQAxis {
|
||||
public:
|
||||
/* These are the methods we override from the base class */
|
||||
pmacAxis(pmacController *pController, int axisNo);
|
||||
virtual ~pmacAxis();
|
||||
asynStatus move(double position, int relative, double min_velocity, double max_velocity, double acceleration);
|
||||
asynStatus moveVelocity(double min_velocity, double max_velocity, double acceleration);
|
||||
asynStatus home(double min_velocity, double max_velocity, double acceleration, int forwards);
|
||||
asynStatus stop(double acceleration);
|
||||
asynStatus poll(bool *moving);
|
||||
asynStatus setPosition(double position);
|
||||
|
||||
/* These are the methods we override from the base class */
|
||||
pmacAxis(pmacController *pController, int axisNo, bool enable = true);
|
||||
virtual ~pmacAxis();
|
||||
asynStatus move(double position, int relative, double min_velocity,
|
||||
double max_velocity, double acceleration);
|
||||
asynStatus moveVelocity(double min_velocity, double max_velocity,
|
||||
double acceleration);
|
||||
asynStatus home(double min_velocity, double max_velocity,
|
||||
double acceleration, int forwards);
|
||||
asynStatus stop(double acceleration);
|
||||
asynStatus poll(bool *moving);
|
||||
asynStatus setPosition(double position);
|
||||
asynStatus enable(int on);
|
||||
|
||||
protected:
|
||||
pmacController *pC_;
|
||||
|
||||
asynStatus getAxisStatus(bool *moving);
|
||||
asynStatus getAxisInitialStatus(void);
|
||||
|
||||
double setpointPosition_;
|
||||
double encoderPosition_;
|
||||
double currentVelocity_;
|
||||
double velocity_;
|
||||
double accel_;
|
||||
double highLimit_;
|
||||
double lowLimit_;
|
||||
double scale_;
|
||||
double previous_position_;
|
||||
int previous_direction_;
|
||||
int encoder_axis_;
|
||||
int axisErrorCount;
|
||||
|
||||
time_t startTime;
|
||||
time_t status6Time;
|
||||
int starting;
|
||||
int homing;
|
||||
double statusPos;
|
||||
|
||||
time_t next_poll;
|
||||
|
||||
bool autoEnable;
|
||||
|
||||
friend class pmacController;
|
||||
friend class pmacV3Controller;
|
||||
};
|
||||
/*--------------------------------------------------------------------------------------------*/
|
||||
class SeleneAxis : public pmacAxis {
|
||||
public:
|
||||
SeleneAxis(SeleneController *pController, int axisNo, double limitTarget);
|
||||
asynStatus move(double position, int relative, double min_velocity,
|
||||
double max_velocity, double acceleration);
|
||||
asynStatus setPosition(double position);
|
||||
asynStatus home(double min_velocity, double max_velocity,
|
||||
double acceleration, int forwards);
|
||||
|
||||
protected:
|
||||
friend class SeleneController;
|
||||
friend class pmacController;
|
||||
|
||||
private:
|
||||
pmacController *pC_;
|
||||
|
||||
asynStatus getAxisStatus(bool *moving);
|
||||
asynStatus getAxisInitialStatus(void);
|
||||
|
||||
double setpointPosition_;
|
||||
double encoderPosition_;
|
||||
double currentVelocity_;
|
||||
double velocity_;
|
||||
double accel_;
|
||||
double highLimit_;
|
||||
double lowLimit_;
|
||||
double scale_;
|
||||
double previous_position_;
|
||||
int previous_direction_;
|
||||
int encoder_axis_;
|
||||
int axisErrorCount;
|
||||
|
||||
time_t startTime;
|
||||
time_t status6Time;
|
||||
int starting;
|
||||
int homing;
|
||||
|
||||
friend class pmacController;
|
||||
double limitTarget;
|
||||
asynStatus getSeleneAxisInitialStatus(void);
|
||||
};
|
||||
|
||||
/*
|
||||
Yet another special set of motors for the Selene Guide at AMOR. Each segment
|
||||
can be lifted or tilted. This is two motors. One acts as a slave and only
|
||||
writes a new target, the other also sets a new target and sends the actual
|
||||
movement command. Both motors are coordianted in the motor controller in order
|
||||
to avoid tension on the guide elements. This gaves rise to the function code
|
||||
LIFTSLAVE and LIFTMASTER.
|
||||
|
||||
In another mode the whole guide can be lifted or tilted. Then motor 1 and 6
|
||||
get new values and one of them sends the drive command. This causes all 6
|
||||
motors to drive synchronously to their new targets. This is implemented
|
||||
through the LIFTSEGMENT function code.
|
||||
|
||||
Mark Koennecke, February 2020
|
||||
|
||||
The axis should not be enabled automatically
|
||||
|
||||
Michele Brambilla, February 2020
|
||||
|
||||
*/
|
||||
class LiftAxis : public pmacAxis {
|
||||
public:
|
||||
/*
|
||||
The default constructor of LiftAxis just forwards to the pmacAxis
|
||||
constructor, which has an optional argument "autoenable" with the default
|
||||
value "true". However, we want that argument to be false, hence we provide
|
||||
an explicit constructor.
|
||||
*/
|
||||
LiftAxis(pmacController *pController, int axisNo)
|
||||
: pmacAxis((pmacController *)pController, axisNo, false) {};
|
||||
asynStatus move(double position, int relative, double min_velocity,
|
||||
double max_velocity, double acceleration);
|
||||
asynStatus poll(bool *moving);
|
||||
asynStatus stop(double acceleration);
|
||||
|
||||
private:
|
||||
int waitStart;
|
||||
};
|
||||
|
||||
/********************************************
|
||||
* Protocol version 3 requires just some minor change
|
||||
*
|
||||
* Michele Brambilla, February 2022
|
||||
********************************************/
|
||||
|
||||
class pmacV3Axis : public pmacAxis {
|
||||
public:
|
||||
pmacV3Axis(pmacController *pController, int axisNo);
|
||||
|
||||
asynStatus move(double position, int relative, double min_velocity,
|
||||
double max_velocity, double acceleration);
|
||||
asynStatus poll(bool *moving);
|
||||
|
||||
protected:
|
||||
int IsEnable;
|
||||
double Speed;
|
||||
|
||||
asynStatus getAxisStatus(bool *moving);
|
||||
|
||||
friend class pmacController;
|
||||
friend class pmacV3Controller;
|
||||
};
|
||||
|
||||
/*----------------------------------------------------------------------------------------------*/
|
||||
class pmacHRPTAxis : public pmacV3Axis {
|
||||
public:
|
||||
pmacHRPTAxis(pmacController *pController, int axisNo)
|
||||
: pmacV3Axis(pController, axisNo) {};
|
||||
/**
|
||||
* Override getAxisStatus in order to read the special parameter indicating
|
||||
* a slit blade crash at HRPT
|
||||
*/
|
||||
asynStatus getAxisStatus(bool *moving);
|
||||
|
||||
protected:
|
||||
friend class pmacController;
|
||||
};
|
||||
|
||||
/*
|
||||
* Special motors for the AMOR detector movement. The whole
|
||||
* command set is different but on a pmac controller. This implements
|
||||
* a coordinated movement of cox, coz and ftz in order not to break
|
||||
* the flight tube which may have been mounted. This is mapped to three
|
||||
* motors selected via the function code: com, the detector omega, coz,
|
||||
* the detector z offset and park, for parking the flightpath.
|
||||
*/
|
||||
|
||||
#define ADCOM 1
|
||||
#define ADCOZ 2
|
||||
#define ADPARK 3
|
||||
|
||||
class AmorDetectorAxis : public pmacAxis {
|
||||
public:
|
||||
AmorDetectorAxis(pmacController *pController, int axisNo, int function);
|
||||
|
||||
asynStatus move(double position, int relative, double min_velocity,
|
||||
double max_velocity, double acceleration);
|
||||
asynStatus poll(bool *moving);
|
||||
asynStatus moveVelocity(double min_velocity, double max_velocity,
|
||||
double acceleration);
|
||||
asynStatus home(double min_velocity, double max_velocity,
|
||||
double acceleration, int forwards);
|
||||
asynStatus stop(double acceleration);
|
||||
asynStatus setPosition(double position);
|
||||
|
||||
protected:
|
||||
int _function;
|
||||
int det_starting;
|
||||
time_t det_startTime;
|
||||
};
|
||||
|
||||
/*----------------------------------------------------------------------------------------------*/
|
||||
|
||||
class GirderAxis : public pmacV3Axis {
|
||||
public:
|
||||
GirderAxis(pmacController *pController, int axisNo);
|
||||
asynStatus move(double position, int relative, double min_velocity,
|
||||
double max_velocity, double acceleration);
|
||||
asynStatus stop(double acceleration);
|
||||
asynStatus poll(bool *moving);
|
||||
|
||||
protected:
|
||||
int IsEnable;
|
||||
|
||||
friend class pmacController;
|
||||
friend class pmacV3Controller;
|
||||
};
|
||||
|
||||
#endif /* pmacAxis_H */
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -7,23 +7,27 @@
|
||||
* Matthew Pearson
|
||||
* 23 May 2012
|
||||
*
|
||||
*
|
||||
* Modified to use the MsgTxt field for SINQ
|
||||
*
|
||||
* Mark Koennecke, January 2019
|
||||
********************************************/
|
||||
|
||||
#ifndef pmacController_H
|
||||
#define pmacController_H
|
||||
|
||||
#include "asynMotorController.h"
|
||||
#include "SINQController.h"
|
||||
#include "asynMotorAxis.h"
|
||||
#include "pmacAxis.h"
|
||||
|
||||
#define PMAC_C_GlobalStatusString "PMAC_C_GLOBALSTATUS"
|
||||
#define PMAC_C_CommsErrorString "PMAC_C_COMMSERROR"
|
||||
|
||||
class pmacController : public asynMotorController {
|
||||
class pmacController : public SINQController {
|
||||
|
||||
public:
|
||||
pmacController(const char *portName, const char *lowLevelPortName, int lowLevelPortAddress, int numAxes, double movingPollPeriod,
|
||||
double idlePollPeriod);
|
||||
pmacController(const char *portName, const char *lowLevelPortName, int lowLevelPortAddress, int numAxes, double movingPollPeriod,
|
||||
double idlePollPeriod, const int& extraParams=2);
|
||||
|
||||
virtual ~pmacController();
|
||||
|
||||
@ -45,13 +49,13 @@ class pmacController : public asynMotorController {
|
||||
int PMAC_C_GlobalStatus_;
|
||||
int PMAC_C_CommsError_;
|
||||
#define LAST_PMAC_PARAM PMAC_C_CommsError__
|
||||
void debugFlow(const char *message);
|
||||
asynStatus lowLevelWriteRead(int axisNo, const char *command, char *response);
|
||||
|
||||
private:
|
||||
asynUser* lowLevelPortUser_;
|
||||
epicsUInt32 debugFlag_;
|
||||
asynStatus lowLevelWriteRead(const char *command, char *response);
|
||||
int lowLevelPortConnect(const char *port, int addr, asynUser **ppasynUser, char *inputEos, char *outputEos);
|
||||
void debugFlow(const char *message);
|
||||
|
||||
//static class data members
|
||||
|
||||
@ -132,9 +136,64 @@ class pmacController : public asynMotorController {
|
||||
static const epicsUInt32 PMAX_AXIS_GENERAL_PROB2;
|
||||
|
||||
friend class pmacAxis;
|
||||
|
||||
friend class pmacHRPTAxis;
|
||||
friend class SeleneAxis;
|
||||
friend class LiftAxis;
|
||||
friend class pmacV3Axis;
|
||||
friend class GirderAxis;
|
||||
friend class AmorDetectorAxis;
|
||||
};
|
||||
|
||||
#define NUM_PMAC_PARAMS (&LAST_PMAC_PARAM - &FIRST_PMAC_PARAM + 1)
|
||||
|
||||
#define MotorSetPositionString "SET_MOTOR_POSITION"
|
||||
|
||||
class SeleneController : public pmacController {
|
||||
public:
|
||||
SeleneController(const char *portName, const char *lowLevelPortName, int lowLevelPortAddress,
|
||||
int numAxes, double movingPollPeriod, double idlePollPeriod);
|
||||
|
||||
~SeleneController(void) { }
|
||||
|
||||
// overloaded because we have a different command to set the limits
|
||||
asynStatus writeFloat64(asynUser *pasynUser, epicsFloat64 value);
|
||||
|
||||
friend class SeleneAxis;
|
||||
friend class pmacAxis;
|
||||
|
||||
protected:
|
||||
int setMotorPosition_;
|
||||
|
||||
};
|
||||
|
||||
#define EnableAxisString "ENABLE_AXIS"
|
||||
#define AxisEnabledString "AXIS_ENABLED"
|
||||
#define ReReadEncoderPositionString "REREAD_ENCODER_POSITION"
|
||||
#define ReReadEncoderPositionString_RBV "REREAD_ENCODER_POSITION_RBV"
|
||||
|
||||
class pmacV3Controller : public pmacController {
|
||||
public:
|
||||
pmacV3Controller(const char *portName, const char *lowLevelPortName,
|
||||
int lowLevelPortAddress, int numAxes, double movingPollPeriod,
|
||||
double idlePollPeriod, const int &extraParams = 4);
|
||||
|
||||
// overloaded because we want to enable/disable the motor
|
||||
asynStatus writeInt32(asynUser *pasynUser, epicsInt32 value);
|
||||
|
||||
// overloaded because we want to read the axis state
|
||||
asynStatus readInt32(asynUser *pasynUser, epicsInt32 *value);
|
||||
|
||||
friend class pmacV3Axis;
|
||||
friend class pmacAxis;
|
||||
friend class GirderAxis;
|
||||
|
||||
protected:
|
||||
pmacV3Axis **pAxes_; /**< Array of pointers to axis objects */
|
||||
|
||||
int enableAxis_;
|
||||
int axisEnabled_;
|
||||
int rereadEncoderPosition_;
|
||||
int rereadEncoderPositionRBV_;
|
||||
};
|
||||
|
||||
#endif /* pmacController_H */
|
||||
|
@ -3,8 +3,13 @@
|
||||
#---------------------------------------------
|
||||
registrar(EL734Register)
|
||||
registrar(PhytronRegister)
|
||||
registrar(EuroMoveRegister)
|
||||
registrar(NanotecRegister)
|
||||
registrar(pmacControllerRegister)
|
||||
# registrar(pmacControllerRegister)
|
||||
registrar(C804ControllerRegister)
|
||||
# registrar(pmacAsynIPPortRegister)
|
||||
registrar(MasterMACSRegister)
|
||||
registrar(SINQControllerRegister)
|
||||
|
||||
#--------------------------------------------------------
|
||||
# With the PSI module build system, including these items actually
|
||||
|
88
testEuroMoveUsb.py
Executable file
88
testEuroMoveUsb.py
Executable file
@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: iso-8859-1 -*-
|
||||
"""
|
||||
Created on Thur Feb 06 10:22:42 2020
|
||||
|
||||
@author: gaston emmanuel exil
|
||||
"""
|
||||
|
||||
#pip install pyusb
|
||||
#sudo cp 10-llbDevices.rules /etc/udev/rules.d/
|
||||
#sudo udevadm control --reload-rules && udevadm trigger
|
||||
#reboot
|
||||
|
||||
import sys
|
||||
import usb.core
|
||||
import usb.util
|
||||
from time import sleep
|
||||
|
||||
# 1. Device
|
||||
llbVendorID=0x04B4
|
||||
llbProductID=0x1002
|
||||
|
||||
# 2. Configuration
|
||||
CONFIGURATION_EV3 = 1 # 1-based
|
||||
# 3. Interface
|
||||
INTERFACE_EV3 = 0 # 0-based
|
||||
# 4. Alternate setting
|
||||
SETTING_EV3 = 0 # 0-based
|
||||
# 5. Endpoint
|
||||
ENDPOINT_EV3 = 1 # 0-based
|
||||
|
||||
# find our device
|
||||
device = usb.core.find(idVendor=llbVendorID, idProduct=llbProductID)
|
||||
|
||||
# was it found?
|
||||
if device is None:
|
||||
raise ValueError('Device not found')
|
||||
sys.exit(1)
|
||||
else:
|
||||
if device._manufacturer is None:
|
||||
device._manufacturer = usb.util.get_string(device, device.iManufacturer)
|
||||
print("manufacturer: ", str(device._manufacturer))
|
||||
if device._product is None:
|
||||
device._product = usb.util.get_string(device, device.iProduct)
|
||||
print("product: ", str(device._product))
|
||||
|
||||
|
||||
# By default, the kernel will claim the device and make it available via
|
||||
# /dev/usb/hiddevN and /dev/hidrawN which also prevents us
|
||||
# from communicating otherwise. This removes these kernel devices.
|
||||
# Yes, it is weird to specify an interface before we get to a configuration.
|
||||
|
||||
if device.is_kernel_driver_active(INTERFACE_EV3):
|
||||
print("Detaching kernel driver")
|
||||
device.detach_kernel_driver(INTERFACE_EV3)
|
||||
|
||||
# claim the device
|
||||
usb.util.claim_interface(device, INTERFACE_EV3)
|
||||
|
||||
# write endpoint
|
||||
endpoint_BulkOut = device[0][(0,0)][0]
|
||||
# Read endpoint
|
||||
endpoint_BulkIn = device[0][(0,0)][2]
|
||||
try:
|
||||
command = "L"+chr(13)
|
||||
assert device.write(endpoint_BulkOut, command.encode('utf-8'), 100) == len(command)
|
||||
ret = device.read(endpoint_BulkIn, endpoint_BulkIn.wMaxPacketSize, timeout=30000)
|
||||
print(ret)
|
||||
#byte_str = "".join(chr(n) for n in ret)
|
||||
print(ret.tostring())
|
||||
|
||||
|
||||
|
||||
command = "A1,4"+chr(13)
|
||||
assert device.write(endpoint_BulkOut, command.encode('utf-8'), 100) == len(command)
|
||||
ret = device.read(endpoint_BulkIn, endpoint_BulkIn.wMaxPacketSize, timeout=30000)
|
||||
print(ret)
|
||||
byte_str = "".join(chr(n) for n in ret)
|
||||
print(ret.tostring())
|
||||
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
|
||||
# release the device
|
||||
usb.util.release_interface(device, INTERFACE_EV3)
|
||||
# reattach the device to the OS kernel
|
||||
#device.attach_kernel_driver(INTERFACE_EV3)
|
23
testel734/db/basic_asyn_motor.db
Normal file
23
testel734/db/basic_asyn_motor.db
Normal file
@ -0,0 +1,23 @@
|
||||
record(motor,"$(P)$(M)")
|
||||
{
|
||||
field(DESC,"$(DESC)")
|
||||
field(DTYP,"$(DTYP)")
|
||||
field(DIR,"$(DIR)")
|
||||
field(VELO,"$(VELO)")
|
||||
field(VBAS,"$(VBAS)")
|
||||
field(ACCL,"$(ACCL)")
|
||||
field(BDST,"$(BDST)")
|
||||
field(BVEL,"$(BVEL)")
|
||||
field(BACC,"$(BACC)")
|
||||
field(OUT,"@asyn($(PORT),$(ADDR))")
|
||||
field(MRES,"$(MRES)")
|
||||
field(PREC,"$(PREC)")
|
||||
field(EGU,"$(EGU)")
|
||||
field(DHLM,"$(DHLM)")
|
||||
field(DLLM,"$(DLLM)")
|
||||
field(INIT,"$(INIT)")
|
||||
field(TWV,"1")
|
||||
field(RDBD,"$(RDBD)")
|
||||
field(RTRY,"0")
|
||||
}
|
||||
|
21
testel734/db/basic_motor.db
Normal file
21
testel734/db/basic_motor.db
Normal file
@ -0,0 +1,21 @@
|
||||
grecord(motor,"$(P)$(M)")
|
||||
{
|
||||
field(DESC,"$(DESC)")
|
||||
field(DTYP,"$(DTYP)")
|
||||
field(DIR,"$(DIR)")
|
||||
field(VELO,"$(VELO)")
|
||||
field(VBAS,"$(VBAS)")
|
||||
field(ACCL,"$(ACCL)")
|
||||
field(BDST,"$(BDST)")
|
||||
field(BVEL,"$(BVEL)")
|
||||
field(BACC,"$(BACC)")
|
||||
field(OUT,"#C$(C) S$(S) @")
|
||||
field(MRES,"$(MRES)")
|
||||
field(PREC,"$(PREC)")
|
||||
field(EGU,"$(EGU)")
|
||||
field(DHLM,"$(DHLM)")
|
||||
field(DLLM,"$(DLLM)")
|
||||
field(INIT,"$(INIT)")
|
||||
field(TWV,"1")
|
||||
}
|
||||
|
18
testel734/db/motorMessage.db
Normal file
18
testel734/db/motorMessage.db
Normal file
@ -0,0 +1,18 @@
|
||||
|
||||
# The message text
|
||||
record(waveform, "$(P)$(M)-MsgTxt") {
|
||||
field(DTYP, "asynOctetRead")
|
||||
field(INP, "@asyn($(PORT),$(N),1) MOTOR_MESSAGE_TEXT")
|
||||
field(FTVL, "CHAR")
|
||||
field(NELM, "80")
|
||||
field(SCAN, "I/O Intr")
|
||||
}
|
||||
|
||||
record(ao,"$(P)m$(N)-Resolution"){
|
||||
field(DESC,"m$(N) Resolution")
|
||||
field(DOL,"$(P)m$(N).MRES CP MS")
|
||||
field(OMSL,"closed_loop")
|
||||
field(DTYP,"asynFloat64")
|
||||
field(OUT,"@asyn($(PORT),$(N))MOTOR_RESOLUTION")
|
||||
field(PREC,"3")
|
||||
}
|
48
testel734/db/sinq_asyn_motor.db
Normal file
48
testel734/db/sinq_asyn_motor.db
Normal file
@ -0,0 +1,48 @@
|
||||
record(motor,"$(P)$(M)")
|
||||
{
|
||||
field(DESC,"$(DESC)")
|
||||
field(DTYP,"$(DTYP)")
|
||||
field(DIR,"$(DIR)")
|
||||
field(VELO,"$(VELO)")
|
||||
field(VMAX,"$(VMAX=$(VELO))")
|
||||
field(VBAS,"$(VBAS)")
|
||||
field(ACCL,"$(ACCL)")
|
||||
field(BDST,"$(BDST)")
|
||||
field(BVEL,"$(BVEL)")
|
||||
field(BACC,"$(BACC)")
|
||||
field(OUT,"@asyn($(PORT),$(ADDR))")
|
||||
field(MRES,"$(MRES)")
|
||||
field(PREC,"$(PREC)")
|
||||
field(EGU,"$(EGU)")
|
||||
field(DHLM,"$(DHLM)")
|
||||
field(DLLM,"$(DLLM)")
|
||||
field(INIT,"$(INIT)")
|
||||
field(TWV,"1")
|
||||
field(RDBD,"$(RDBD)")
|
||||
}
|
||||
|
||||
|
||||
# The message text
|
||||
record(waveform, "$(P)$(M)-MsgTxt") {
|
||||
field(DTYP, "asynOctetRead")
|
||||
field(INP, "@asyn($(PORT),$(N),1) MOTOR_MESSAGE_TEXT")
|
||||
field(FTVL, "CHAR")
|
||||
field(NELM, "80")
|
||||
field(SCAN, "I/O Intr")
|
||||
}
|
||||
|
||||
# enable axis
|
||||
record(longout, "$(P)$(M):Enable") {
|
||||
field(DTYP, "asynInt32")
|
||||
field(OUT, "@asyn($(PORT),$(N),1) ENABLE_AXIS")
|
||||
field(PINI, "YES")
|
||||
}
|
||||
|
||||
# enable axis
|
||||
record(longin, "$(P)$(M):Enable_RBV") {
|
||||
field(DTYP, "asynInt32")
|
||||
field(INP, "@asyn($(PORT),$(N),1) AXIS_ENABLED")
|
||||
field(PINI, "YES")
|
||||
field(SCAN, "1 second")
|
||||
}
|
||||
|
13
testel734/mota.sub
Normal file
13
testel734/mota.sub
Normal file
@ -0,0 +1,13 @@
|
||||
file "$(TOP)/db/basic_asyn_motor.db"
|
||||
{
|
||||
pattern
|
||||
{P, N, M, DTYP, PORT, ADDR, DESC, EGU, DIR, VELO, VBAS, ACCL, BDST, BVEL, BACC, MRES, PREC, DLLM, DHLM, INIT}
|
||||
{KM36:mota:, 2, "m$(N)", "asynMotor", mota, 2, "sgl", degree, Pos, 2.0, 0.1, .2, 0, 1, .2, 1., 3, -20.0, 20.0, ""}
|
||||
}
|
||||
|
||||
file "$(TOP)/db/motorMessage.db"
|
||||
{
|
||||
pattern
|
||||
{P,N, M,PORT}
|
||||
{KM36:mota:, 2, "m$(N)",mota}
|
||||
}
|
12
testel734/motaspeed.sub
Normal file
12
testel734/motaspeed.sub
Normal file
@ -0,0 +1,12 @@
|
||||
file "$(TOP)/db/sinq_asyn_motor.db"
|
||||
{
|
||||
pattern
|
||||
{P, N, M, DTYP, PORT, ADDR, DESC, EGU, DIR, VELO, VBAS, ACCL, BDST, BVEL, BACC, MRES, PREC, DLLM, DHLM, INIT, RDBD}
|
||||
{KM36:mota:,2,"m$(N)","asynMotor", mota, 2, "sgl", degree, Pos, 100, 0.1, .2, 0, 1, .2, 1., 3,-180.0,360.0,"", 0.2}}
|
||||
|
||||
file "$(TOP)/db/motorMessage.db"
|
||||
{
|
||||
pattern
|
||||
{P,N, M,PORT}
|
||||
{KM36:mota:, 2, "m$(N)",mota}
|
||||
}
|
12
testel734/testel734.cmd
Executable file
12
testel734/testel734.cmd
Executable file
@ -0,0 +1,12 @@
|
||||
#!/usr/local/bin/iocsh
|
||||
|
||||
require sinq,koennecke
|
||||
|
||||
epicsEnvSet("TOP","/afs/psi.ch/project/sinqdev/sinqepicsapp/testel734")
|
||||
|
||||
drvAsynIPPortConfigure("serial1", "localhost:61000",0,0,0)
|
||||
EL734CreateController("mota","serial1",3)
|
||||
|
||||
#dbLoadTemplate "mota.sub"
|
||||
dbLoadTemplate "motaspeed.sub"
|
||||
|
11
testel737.cmd
Executable file
11
testel737.cmd
Executable file
@ -0,0 +1,11 @@
|
||||
#!/usr/local/bin/iocsh
|
||||
|
||||
require sinq,koennecke
|
||||
|
||||
epicsEnvSet("TOP","/afs/psi.ch/project/sinqdev/sinqepicsapp")
|
||||
|
||||
#--------- load EL737 counter box
|
||||
drvAsynIPPortConfigure("cter1","localhost:62000",0,0,0)
|
||||
dbLoadRecords("$(TOP)/db/asynRecord.db","P=KM36:,R=cter1,PORT=cter1,ADDR=0,OMAX=80,IMAX=80")
|
||||
dbLoadRecords("${TOP}/db/el737Record.db","P=KM36:counter,PORT=cter1,DESC=SANSCounter")
|
||||
|
9
testhuber/db/asynRecord.db
Normal file
9
testhuber/db/asynRecord.db
Normal file
@ -0,0 +1,9 @@
|
||||
record(asyn,"$(P)$(R)")
|
||||
{
|
||||
field(DTYP,"asynRecordDevice")
|
||||
field(PORT,"$(PORT)")
|
||||
field(ADDR,"$(ADDR)")
|
||||
field(OMAX,"$(OMAX)")
|
||||
field(IMAX,"$(IMAX)")
|
||||
}
|
||||
|
23
testhuber/db/basic_asyn_motor.db
Normal file
23
testhuber/db/basic_asyn_motor.db
Normal file
@ -0,0 +1,23 @@
|
||||
record(motor,"$(P)$(M)")
|
||||
{
|
||||
field(DESC,"$(DESC)")
|
||||
field(DTYP,"$(DTYP)")
|
||||
field(DIR,"$(DIR)")
|
||||
field(VELO,"$(VELO)")
|
||||
field(VBAS,"$(VBAS)")
|
||||
field(ACCL,"$(ACCL)")
|
||||
field(BDST,"$(BDST)")
|
||||
field(BVEL,"$(BVEL)")
|
||||
field(BACC,"$(BACC)")
|
||||
field(OUT,"@asyn($(PORT),$(ADDR))")
|
||||
field(MRES,"$(MRES)")
|
||||
field(PREC,"$(PREC)")
|
||||
field(EGU,"$(EGU)")
|
||||
field(DHLM,"$(DHLM)")
|
||||
field(DLLM,"$(DLLM)")
|
||||
field(INIT,"$(INIT)")
|
||||
field(TWV,"1")
|
||||
field(RDBD,"$(RDBD)")
|
||||
field(RTRY,"0")
|
||||
}
|
||||
|
21
testhuber/db/basic_motor.db
Normal file
21
testhuber/db/basic_motor.db
Normal file
@ -0,0 +1,21 @@
|
||||
grecord(motor,"$(P)$(M)")
|
||||
{
|
||||
field(DESC,"$(DESC)")
|
||||
field(DTYP,"$(DTYP)")
|
||||
field(DIR,"$(DIR)")
|
||||
field(VELO,"$(VELO)")
|
||||
field(VBAS,"$(VBAS)")
|
||||
field(ACCL,"$(ACCL)")
|
||||
field(BDST,"$(BDST)")
|
||||
field(BVEL,"$(BVEL)")
|
||||
field(BACC,"$(BACC)")
|
||||
field(OUT,"#C$(C) S$(S) @")
|
||||
field(MRES,"$(MRES)")
|
||||
field(PREC,"$(PREC)")
|
||||
field(EGU,"$(EGU)")
|
||||
field(DHLM,"$(DHLM)")
|
||||
field(DLLM,"$(DLLM)")
|
||||
field(INIT,"$(INIT)")
|
||||
field(TWV,"1")
|
||||
}
|
||||
|
18
testhuber/db/motorMessage.db
Normal file
18
testhuber/db/motorMessage.db
Normal file
@ -0,0 +1,18 @@
|
||||
|
||||
# The message text
|
||||
record(waveform, "$(P)$(M)-MsgTxt") {
|
||||
field(DTYP, "asynOctetRead")
|
||||
field(INP, "@asyn($(PORT),$(N),1) MOTOR_MESSAGE_TEXT")
|
||||
field(FTVL, "CHAR")
|
||||
field(NELM, "80")
|
||||
field(SCAN, "I/O Intr")
|
||||
}
|
||||
|
||||
record(ao,"$(P)m$(N)-Resolution"){
|
||||
field(DESC,"m$(N) Resolution")
|
||||
field(DOL,"$(P)m$(N).MRES CP MS")
|
||||
field(OMSL,"closed_loop")
|
||||
field(DTYP,"asynFloat64")
|
||||
field(OUT,"@asyn($(PORT),$(N))MOTOR_RESOLUTION")
|
||||
field(PREC,"3")
|
||||
}
|
14
testhuber/db/pmacV3.db
Normal file
14
testhuber/db/pmacV3.db
Normal file
@ -0,0 +1,14 @@
|
||||
# enable axis
|
||||
record(longout, "$(P)$(M):Enable") {
|
||||
field(DTYP, "asynInt32")
|
||||
field(OUT, "@asyn($(PORT),$(N),1) ENABLE_AXIS")
|
||||
field(PINI, "YES")
|
||||
}
|
||||
|
||||
# enable axis
|
||||
record(longin, "$(P)$(M):Enable_RBV") {
|
||||
field(DTYP, "asynInt32")
|
||||
field(INP, "@asyn($(PORT),$(N),1) AXIS_ENABLED")
|
||||
field(PINI, "YES")
|
||||
field(SCAN, "1 second")
|
||||
}
|
47
testhuber/db/sinq_asyn_motor.db
Normal file
47
testhuber/db/sinq_asyn_motor.db
Normal file
@ -0,0 +1,47 @@
|
||||
record(motor,"$(P)$(M)")
|
||||
{
|
||||
field(DESC,"$(DESC)")
|
||||
field(DTYP,"$(DTYP)")
|
||||
field(DIR,"$(DIR)")
|
||||
field(VELO,"$(VELO)")
|
||||
field(VBAS,"$(VBAS)")
|
||||
field(ACCL,"$(ACCL)")
|
||||
field(BDST,"$(BDST)")
|
||||
field(BVEL,"$(BVEL)")
|
||||
field(BACC,"$(BACC)")
|
||||
field(OUT,"@asyn($(PORT),$(ADDR))")
|
||||
field(MRES,"$(MRES)")
|
||||
field(PREC,"$(PREC)")
|
||||
field(EGU,"$(EGU)")
|
||||
field(DHLM,"$(DHLM)")
|
||||
field(DLLM,"$(DLLM)")
|
||||
field(INIT,"$(INIT)")
|
||||
field(TWV,"1")
|
||||
field(RDBD,"$(RDBD)")
|
||||
}
|
||||
|
||||
|
||||
# The message text
|
||||
record(waveform, "$(P)$(M)-MsgTxt") {
|
||||
field(DTYP, "asynOctetRead")
|
||||
field(INP, "@asyn($(PORT),$(N),1) MOTOR_MESSAGE_TEXT")
|
||||
field(FTVL, "CHAR")
|
||||
field(NELM, "80")
|
||||
field(SCAN, "I/O Intr")
|
||||
}
|
||||
|
||||
# enable axis
|
||||
record(longout, "$(P)$(M):Enable") {
|
||||
field(DTYP, "asynInt32")
|
||||
field(OUT, "@asyn($(PORT),$(N),1) ENABLE_AXIS")
|
||||
field(PINI, "YES")
|
||||
}
|
||||
|
||||
# enable axis
|
||||
record(longin, "$(P)$(M):Enable_RBV") {
|
||||
field(DTYP, "asynInt32")
|
||||
field(INP, "@asyn($(PORT),$(N),1) AXIS_ENABLED")
|
||||
field(PINI, "YES")
|
||||
field(SCAN, "5 second")
|
||||
}
|
||||
|
33
testhuber/huber.cmd
Executable file
33
testhuber/huber.cmd
Executable file
@ -0,0 +1,33 @@
|
||||
#!/ioc/tools/iocsh
|
||||
|
||||
require motorHuber,brambilla_m
|
||||
|
||||
# epicsEnvSet("TOP","/ioc/sinq-ioc/boa-ioc")
|
||||
|
||||
epicsEnvSet("TOP","/afs/psi.ch/project/sinqdev/sinqepicsapp/testhuber")
|
||||
|
||||
epicsEnvSet("STREAM_PROTOCOL_PATH","./db")
|
||||
epicsEnvSet("MOTOR",".")
|
||||
|
||||
# require autosave, koennecke
|
||||
|
||||
# Configure IP Port
|
||||
drvAsynIPPortConfigure("Huber1", "129.129.195.128:1234",0,0,0)
|
||||
|
||||
# Configure Controller
|
||||
asynOctetSetInputEos( "Huber1", -1, "\r")
|
||||
asynOctetSetOutputEos("Huber1", -1, "\r")
|
||||
|
||||
SMC9300CreateController("SMC1", "Huber1", 5, 50, 2000)
|
||||
|
||||
dbLoadTemplate "motor.substitutions.smc9300"
|
||||
|
||||
dbLoadRecords("$(TOP)/db/asynRecord.db","P=SQ:BOA:optics:,R=mcu,PORT=Huber1,ADDR=0,OMAX=80,IMAX=80")
|
||||
|
||||
|
||||
#--------------------------------- restore autosave
|
||||
#set_requestfile_path("$(TOP)", "")
|
||||
#set_savefile_path("$(TOP)/autosave")
|
||||
#save_restoreSet_Debug(0)
|
||||
#set_pass0_restoreFile("$(TOP)/autosave/huber.sav","")
|
||||
#set_pass1_restoreFile("$(TOP)/autosave/huber.sav","")
|
19
testhuber/motor.substitutions.smc9300
Normal file
19
testhuber/motor.substitutions.smc9300
Normal file
@ -0,0 +1,19 @@
|
||||
file "$(MOTOR)/db/basic_asyn_motor.db"
|
||||
{
|
||||
pattern
|
||||
{P, N, M, DTYP, PORT, ADDR,DESC, EGU, DIR, VELO, VBAS, ACCL, BDST, BVEL, BACC, MRES, PREC, DHLM, DLLM, RDBD, INIT}
|
||||
{SQ:BOA:optics:, 1, "trans1","asynMotor", SMC1, 1, "translation 1",mm, Pos, 200, 10, 20, 0, 1, .2, 0.0001, 4, 150, -150, 0.1, ""}
|
||||
{SQ:BOA:optics:, 2, "lift1", "asynMotor", SMC1, 2, "lift 1", mm, Pos, 200, 10, 80, 0, 1, .2, 0.0001, 4, 100, 0, 0.1, ""}
|
||||
{SQ:BOA:optics:, 3, "trans2","asynMotor", SMC1, 3, "translation 2",mm, Pos, 200, 10, 80, 0, 1, .2, 0.0001, 4, 150, -150, 0.1, ""}
|
||||
{SQ:BOA:optics:, 4, "lift2", "asynMotor", SMC1, 4, "lift 2" ,mm, Pos, 200, 10, 80, 0, 1, .2, 0.0001, 4, 100, 0, 0.1, ""}
|
||||
}
|
||||
|
||||
file "$(TOP)/db/motorMessage.db"
|
||||
{
|
||||
pattern
|
||||
{P,N, M,PORT}
|
||||
{SQ:BOA:optics:, 1, "trans1", SMC1}
|
||||
{SQ:BOA:optics:, 2, "lift1", SMC1}
|
||||
{SQ:BOA:optics:, 3, "trans2", SMC1}
|
||||
{SQ:BOA:optics:, 4, "lift2", SMC1}
|
||||
}
|
9
testmmac/db/asynRecord.db
Normal file
9
testmmac/db/asynRecord.db
Normal file
@ -0,0 +1,9 @@
|
||||
record(asyn,"$(P)$(R)")
|
||||
{
|
||||
field(DTYP,"asynRecordDevice")
|
||||
field(PORT,"$(PORT)")
|
||||
field(ADDR,"$(ADDR)")
|
||||
field(OMAX,"$(OMAX)")
|
||||
field(IMAX,"$(IMAX)")
|
||||
}
|
||||
|
23
testmmac/db/basic_asyn_motor.db
Normal file
23
testmmac/db/basic_asyn_motor.db
Normal file
@ -0,0 +1,23 @@
|
||||
record(motor,"$(P)$(M)")
|
||||
{
|
||||
field(DESC,"$(DESC)")
|
||||
field(DTYP,"$(DTYP)")
|
||||
field(DIR,"$(DIR)")
|
||||
field(VELO,"$(VELO)")
|
||||
field(VBAS,"$(VBAS)")
|
||||
field(ACCL,"$(ACCL)")
|
||||
field(BDST,"$(BDST)")
|
||||
field(BVEL,"$(BVEL)")
|
||||
field(BACC,"$(BACC)")
|
||||
field(OUT,"@asyn($(PORT),$(ADDR))")
|
||||
field(MRES,"$(MRES)")
|
||||
field(PREC,"$(PREC)")
|
||||
field(EGU,"$(EGU)")
|
||||
field(DHLM,"$(DHLM)")
|
||||
field(DLLM,"$(DLLM)")
|
||||
field(INIT,"$(INIT)")
|
||||
field(TWV,"1")
|
||||
field(RDBD,"$(RDBD)")
|
||||
field(RTRY,"0")
|
||||
}
|
||||
|
21
testmmac/db/basic_motor.db
Normal file
21
testmmac/db/basic_motor.db
Normal file
@ -0,0 +1,21 @@
|
||||
grecord(motor,"$(P)$(M)")
|
||||
{
|
||||
field(DESC,"$(DESC)")
|
||||
field(DTYP,"$(DTYP)")
|
||||
field(DIR,"$(DIR)")
|
||||
field(VELO,"$(VELO)")
|
||||
field(VBAS,"$(VBAS)")
|
||||
field(ACCL,"$(ACCL)")
|
||||
field(BDST,"$(BDST)")
|
||||
field(BVEL,"$(BVEL)")
|
||||
field(BACC,"$(BACC)")
|
||||
field(OUT,"#C$(C) S$(S) @")
|
||||
field(MRES,"$(MRES)")
|
||||
field(PREC,"$(PREC)")
|
||||
field(EGU,"$(EGU)")
|
||||
field(DHLM,"$(DHLM)")
|
||||
field(DLLM,"$(DLLM)")
|
||||
field(INIT,"$(INIT)")
|
||||
field(TWV,"1")
|
||||
}
|
||||
|
18
testmmac/db/motorMessage.db
Normal file
18
testmmac/db/motorMessage.db
Normal file
@ -0,0 +1,18 @@
|
||||
|
||||
# The message text
|
||||
record(waveform, "$(P)$(M)-MsgTxt") {
|
||||
field(DTYP, "asynOctetRead")
|
||||
field(INP, "@asyn($(PORT),$(N),1) MOTOR_MESSAGE_TEXT")
|
||||
field(FTVL, "CHAR")
|
||||
field(NELM, "80")
|
||||
field(SCAN, "I/O Intr")
|
||||
}
|
||||
|
||||
record(ao,"$(P)m$(N)-Resolution"){
|
||||
field(DESC,"m$(N) Resolution")
|
||||
field(DOL,"$(P)m$(N).MRES CP MS")
|
||||
field(OMSL,"closed_loop")
|
||||
field(DTYP,"asynFloat64")
|
||||
field(OUT,"@asyn($(PORT),$(N))MOTOR_RESOLUTION")
|
||||
field(PREC,"3")
|
||||
}
|
14
testmmac/db/pmacV3.db
Normal file
14
testmmac/db/pmacV3.db
Normal file
@ -0,0 +1,14 @@
|
||||
# enable axis
|
||||
record(longout, "$(P)$(M):Enable") {
|
||||
field(DTYP, "asynInt32")
|
||||
field(OUT, "@asyn($(PORT),$(N),1) ENABLE_AXIS")
|
||||
field(PINI, "YES")
|
||||
}
|
||||
|
||||
# enable axis
|
||||
record(longin, "$(P)$(M):Enable_RBV") {
|
||||
field(DTYP, "asynInt32")
|
||||
field(INP, "@asyn($(PORT),$(N),1) AXIS_ENABLED")
|
||||
field(PINI, "YES")
|
||||
field(SCAN, "1 second")
|
||||
}
|
47
testmmac/db/sinq_asyn_motor.db
Normal file
47
testmmac/db/sinq_asyn_motor.db
Normal file
@ -0,0 +1,47 @@
|
||||
record(motor,"$(P)$(M)")
|
||||
{
|
||||
field(DESC,"$(DESC)")
|
||||
field(DTYP,"$(DTYP)")
|
||||
field(DIR,"$(DIR)")
|
||||
field(VELO,"$(VELO)")
|
||||
field(VBAS,"$(VBAS)")
|
||||
field(ACCL,"$(ACCL)")
|
||||
field(BDST,"$(BDST)")
|
||||
field(BVEL,"$(BVEL)")
|
||||
field(BACC,"$(BACC)")
|
||||
field(OUT,"@asyn($(PORT),$(ADDR))")
|
||||
field(MRES,"$(MRES)")
|
||||
field(PREC,"$(PREC)")
|
||||
field(EGU,"$(EGU)")
|
||||
field(DHLM,"$(DHLM)")
|
||||
field(DLLM,"$(DLLM)")
|
||||
field(INIT,"$(INIT)")
|
||||
field(TWV,"1")
|
||||
field(RDBD,"$(RDBD)")
|
||||
}
|
||||
|
||||
|
||||
# The message text
|
||||
record(waveform, "$(P)$(M)-MsgTxt") {
|
||||
field(DTYP, "asynOctetRead")
|
||||
field(INP, "@asyn($(PORT),$(N),1) MOTOR_MESSAGE_TEXT")
|
||||
field(FTVL, "CHAR")
|
||||
field(NELM, "80")
|
||||
field(SCAN, "I/O Intr")
|
||||
}
|
||||
|
||||
# enable axis
|
||||
record(longout, "$(P)$(M):Enable") {
|
||||
field(DTYP, "asynInt32")
|
||||
field(OUT, "@asyn($(PORT),$(N),1) ENABLE_AXIS")
|
||||
field(PINI, "YES")
|
||||
}
|
||||
|
||||
# enable axis
|
||||
record(longin, "$(P)$(M):Enable_RBV") {
|
||||
field(DTYP, "asynInt32")
|
||||
field(INP, "@asyn($(PORT),$(N),1) AXIS_ENABLED")
|
||||
field(PINI, "YES")
|
||||
field(SCAN, "5 second")
|
||||
}
|
||||
|
15
testmmac/mmacs.sub
Normal file
15
testmmac/mmacs.sub
Normal file
@ -0,0 +1,15 @@
|
||||
file "$(TOP)/db/basic_asyn_motor.db"
|
||||
{
|
||||
pattern
|
||||
{P, N, M, DTYP, PORT, ADDR, DESC, EGU, DIR, VELO, VBAS, ACCL, BDST, BVEL, BACC, MRES, PREC, DLLM, DHLM, INIT}
|
||||
{KM36:mota:, 5, "m$(N)", "asynMotor", mota, 5, "m5", degree, Pos, 2.0, 0.1, .2, 0, 1, .2, 1., 3, -20.0, 20.0, ""}
|
||||
{KM36:mota:, 6, "m$(N)", "asynMotor", mota, 6, "m6", degree, Pos, 2.0, 0.1, .2, 0, 1, .2, 1., 3, -20.0, 20.0, ""}
|
||||
}
|
||||
|
||||
file "$(TOP)/db/motorMessage.db"
|
||||
{
|
||||
pattern
|
||||
{P,N, M,PORT}
|
||||
{KM36:mota:, 5, "m$(N)",mota}
|
||||
{KM36:mota:, 6, "m$(N)",mota}
|
||||
}
|
8
testmmac/mmacs2.sub
Normal file
8
testmmac/mmacs2.sub
Normal file
@ -0,0 +1,8 @@
|
||||
file "$(TOP)/db/sinq_asyn_motor.db"
|
||||
{
|
||||
pattern
|
||||
{P, N, M, DTYP, PORT, ADDR, DESC, EGU, DIR, VELO, VBAS, ACCL, BDST, BVEL, BACC, MRES, PREC, DLLM, DHLM, RDBD, INIT}
|
||||
{KM36:mota:,5,"m$(N)","asynMotor", mota, 5, "m5", degree, Pos, 2.0, 0.1, .2, 0, 1, .2, .001, 3,-400000.000,400000.000,.01, ""}
|
||||
{KM36:mota:,6,"m$(N)","asynMotor", mota, 6, "m6", degree, Pos, 2.0, 0.1, .2, 0, 1, .2, .001, 3,-200000.000,200000.000,.01, ""}
|
||||
}
|
||||
|
13
testmmac/mota.sub
Normal file
13
testmmac/mota.sub
Normal file
@ -0,0 +1,13 @@
|
||||
file "$(TOP)/db/basic_asyn_motor.db"
|
||||
{
|
||||
pattern
|
||||
{P, N, M, DTYP, PORT, ADDR, DESC, EGU, DIR, VELO, VBAS, ACCL, BDST, BVEL, BACC, MRES, PREC, DLLM, DHLM, INIT}
|
||||
{KM36:mota:, 2, "m$(N)", "asynMotor", mota, 2, "sgl", degree, Pos, 2.0, 0.1, .2, 0, 1, .2, 1., 3, -20.0, 20.0, ""}
|
||||
}
|
||||
|
||||
file "$(TOP)/db/motorMessage.db"
|
||||
{
|
||||
pattern
|
||||
{P,N, M,PORT}
|
||||
{KM36:mota:, 2, "m$(N)",mota}
|
||||
}
|
12
testmmac/motaspeed.sub
Normal file
12
testmmac/motaspeed.sub
Normal file
@ -0,0 +1,12 @@
|
||||
file "$(TOP)/db/sinq_asyn_motor.db"
|
||||
{
|
||||
pattern
|
||||
{P, N, M, DTYP, PORT, ADDR, DESC, EGU, DIR, VELO, VBAS, ACCL, BDST, BVEL, BACC, MRES, PREC, DLLM, DHLM, INIT, RDBD}
|
||||
{KM36:mota:,2,"m$(N)","asynMotor", mota, 2, "sgl", degree, Pos, 100, 0.1, .2, 0, 1, .2, 1., 3,-180.0,360.0,"", 0.2}}
|
||||
|
||||
file "$(TOP)/db/motorMessage.db"
|
||||
{
|
||||
pattern
|
||||
{P,N, M,PORT}
|
||||
{KM36:mota:, 2, "m$(N)",mota}
|
||||
}
|
35
testmmac/testmmacs.cmd
Executable file
35
testmmac/testmmacs.cmd
Executable file
@ -0,0 +1,35 @@
|
||||
#!/usr/local/bin/iocsh
|
||||
|
||||
require sinq,koennecke
|
||||
require autosave,koennecke
|
||||
|
||||
epicsEnvSet("TOP","/afs/psi.ch/project/sinqdev/sinqepicsapp/testmmac")
|
||||
|
||||
set_requestfile_path("$(TOP)", "")
|
||||
set_savefile_path("$(TOP)/autosave")
|
||||
save_restoreSet_Debug(0)
|
||||
#--------------------------------- restore autosave
|
||||
set_pass0_restoreFile("$(TOP)/autosave/test.sav","")
|
||||
set_pass1_restoreFile("$(TOP)/autosave/test.sav","")
|
||||
|
||||
|
||||
#drvAsynMMACSPortConfigure("macs1", "localhost:8080",0,0,0)
|
||||
#asynInterposeEosConfig("macs1", 0, 0, 0)
|
||||
#dbLoadRecords("$(TOP)/db/asynRecord.db","P=KM36:,R=macs1,PORT=macs1,ADDR=0,OMAX=80,IMAX=80")
|
||||
#asynSetTraceMask("macs1", 0, 255)
|
||||
|
||||
#asynSetTraceMask("macs1", 0, 255)
|
||||
#drvAsynIPPortConfigure("macs1", "localhost:8080",0,0,0)
|
||||
drvAsynIPPortConfigure("macs1", "Marcel1--MACS:1917")
|
||||
MasterMACSCreateController("mota","macs1",7, 500, 10000)
|
||||
MasterMACSCreateAxis("mota",5)
|
||||
MasterMACSCreateAxis("mota",6)
|
||||
|
||||
dbLoadTemplate "mmacs2.sub"
|
||||
|
||||
iocInit()
|
||||
setMovingPollPeriod("mota", .5)
|
||||
setIdlePollPeriod("mota", 10. )
|
||||
|
||||
create_monitor_set("test.req", 10, "")
|
||||
|
9
testpmacV3/db/asynRecord.db
Normal file
9
testpmacV3/db/asynRecord.db
Normal file
@ -0,0 +1,9 @@
|
||||
record(asyn,"$(P)$(R)")
|
||||
{
|
||||
field(DTYP,"asynRecordDevice")
|
||||
field(PORT,"$(PORT)")
|
||||
field(ADDR,"$(ADDR)")
|
||||
field(OMAX,"$(OMAX)")
|
||||
field(IMAX,"$(IMAX)")
|
||||
}
|
||||
|
22
testpmacV3/db/basic_asyn_motor.db
Normal file
22
testpmacV3/db/basic_asyn_motor.db
Normal file
@ -0,0 +1,22 @@
|
||||
record(motor,"$(P)$(M)")
|
||||
{
|
||||
field(DESC,"$(DESC)")
|
||||
field(DTYP,"$(DTYP)")
|
||||
field(DIR,"$(DIR)")
|
||||
field(VELO,"$(VELO)")
|
||||
field(VBAS,"$(VBAS)")
|
||||
field(ACCL,"$(ACCL)")
|
||||
field(BDST,"$(BDST)")
|
||||
field(BVEL,"$(BVEL)")
|
||||
field(BACC,"$(BACC)")
|
||||
field(OUT,"@asyn($(PORT),$(ADDR))")
|
||||
field(MRES,"$(MRES)")
|
||||
field(PREC,"$(PREC)")
|
||||
field(EGU,"$(EGU)")
|
||||
field(DHLM,"$(DHLM)")
|
||||
field(DLLM,"$(DLLM)")
|
||||
field(INIT,"$(INIT)")
|
||||
field(TWV,"1")
|
||||
field(RTRY,"0")
|
||||
}
|
||||
|
19544
testpmacV3/db/huber.dbd
Normal file
19544
testpmacV3/db/huber.dbd
Normal file
File diff suppressed because it is too large
Load Diff
9
testpmacV3/db/motorMessage.db
Normal file
9
testpmacV3/db/motorMessage.db
Normal file
@ -0,0 +1,9 @@
|
||||
|
||||
# The message text
|
||||
record(waveform, "$(P)$(M)-MsgTxt") {
|
||||
field(DTYP, "asynOctetRead")
|
||||
field(INP, "@asyn($(PORT),$(N),1) MOTOR_MESSAGE_TEXT")
|
||||
field(FTVL, "CHAR")
|
||||
field(NELM, "80")
|
||||
field(SCAN, "I/O Intr")
|
||||
}
|
14
testpmacV3/db/pmacV3.db
Normal file
14
testpmacV3/db/pmacV3.db
Normal file
@ -0,0 +1,14 @@
|
||||
# enable axis
|
||||
record(longout, "$(P)$(M):Enable") {
|
||||
field(DTYP, "asynInt32")
|
||||
field(OUT, "@asyn($(PORT),$(N),1) ENABLE_AXIS")
|
||||
field(PINI, "YES")
|
||||
}
|
||||
|
||||
# enable axis
|
||||
record(longin, "$(P)$(M):Enable_RBV") {
|
||||
field(DTYP, "asynInt32")
|
||||
field(INP, "@asyn($(PORT),$(N),1) AXIS_ENABLED")
|
||||
field(PINI, "YES")
|
||||
field(SCAN, "1 second")
|
||||
}
|
27
testpmacV3/mcu.substitutions
Normal file
27
testpmacV3/mcu.substitutions
Normal file
@ -0,0 +1,27 @@
|
||||
file "$(TOP)/db/basic_asyn_motor.db"
|
||||
{
|
||||
pattern
|
||||
{P, N, M, DTYP, PORT, ADDR, DESC, EGU, DIR, VELO, VBAS, ACCL, BDST, BVEL, BACC, MRES, PREC, DHLM, DLLM, INIT}
|
||||
{SQ:TEST:mcu:, 2, "m$(N)", "asynMotor", mcu, 2, "m$(N)", degrees, Pos, 2.0, 0.1, .2, 0, 1, .2, .001, 3, 358., 2., ""}
|
||||
{SQ:TEST:mcu:, 6, "m$(N)", "asynMotor", mcu, 6, "m$(N)", degrees, Pos, 2.0, 0.1, .2, 0, 1, .2, .001, 3, 358., 2., ""}
|
||||
{SQ:TEST:mcu:,10, "m$(N)", "asynMotor", mcu,10, "m$(N)", degrees, Pos, 2.0, 0.1, .2, 0, 1, .2, .001, 3, 358., 2., ""}
|
||||
}
|
||||
|
||||
file "$(TOP)/db/motorMessage.db"
|
||||
{
|
||||
pattern
|
||||
{P,N, M,PORT}
|
||||
{SQ:TEST:mcu:, 2, "m$(N)", mcu}
|
||||
{SQ:TEST:mcu:, 6, "m$(N)", mcu}
|
||||
{SQ:TEST:mcu:,10, "m$(N)", mcu}
|
||||
}
|
||||
|
||||
file "$(TOP)/db/pmacV3.db"
|
||||
{
|
||||
pattern
|
||||
{P,N, M,PORT}
|
||||
{SQ:TEST:mcu:, 2, "m$(N)", mcu}
|
||||
{SQ:TEST:mcu:, 6, "m$(N)", mcu}
|
||||
{SQ:TEST:mcu:,10, "m$(N)", mcu}
|
||||
}
|
||||
|
25
testpmacV3/st.cmd
Executable file
25
testpmacV3/st.cmd
Executable file
@ -0,0 +1,25 @@
|
||||
#!/ioc/tools/iocsh
|
||||
|
||||
require sinq,brambilla_m
|
||||
#require sinq,koennecke
|
||||
|
||||
epicsEnvSet("TOP","/afs/psi.ch/project/sinqdev/sinqepicsapp/testpmacV3")
|
||||
|
||||
epicsEnvSet("EPICS_CA_ADDR_LIST","127.0.0.1")
|
||||
|
||||
epicsEnvSet("STREAM_PROTOCOL_PATH","$(TOP)/db")
|
||||
|
||||
|
||||
var streamDebug 3
|
||||
|
||||
# motors
|
||||
|
||||
pmacAsynIPConfigure("pmacV3", "129.129.138.234:1025")
|
||||
pmacV3CreateController("mcu","pmacV3",0,16,50,1000);
|
||||
pmacV3CreateAxis("mcu",2,0);
|
||||
pmacV3CreateAxis("mcu",6,0);
|
||||
pmacV3CreateAxis("mcu",10,0);
|
||||
dbLoadTemplate "$(TOP)/mcu.substitutions"
|
||||
dbLoadRecords("$(TOP)/db/asynRecord.db","P=SQ:TEST:,R=mcu,PORT=pmacV3,ADDR=0,OMAX=80,IMAX=80")
|
||||
|
||||
iocInit
|
56
utils/convertsub.py
Executable file
56
utils/convertsub.py
Executable file
@ -0,0 +1,56 @@
|
||||
#!/usr/bin/python
|
||||
"""
|
||||
This is a little program which generates soft motor initialisation lines and iocsh morsels
|
||||
from a motor substitutions file.
|
||||
|
||||
Mark Koennecke, December 2019
|
||||
"""
|
||||
import sys
|
||||
import pdb
|
||||
import os
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
print('Usage:\n\tconvertsub XXXX.substitutions')
|
||||
sys.exit(1)
|
||||
|
||||
subLen = 0
|
||||
motors = []
|
||||
port = None
|
||||
|
||||
with open(sys.argv[1],'r') as fin:
|
||||
line = fin.readline()
|
||||
while line:
|
||||
line = line.replace(' ','')
|
||||
line = line.strip('{}')
|
||||
l = line.split(',')
|
||||
if line.find('DHLM') > 0: # that is the index discovery line
|
||||
lowlimidx = l.index('DLLM')
|
||||
highlimidx = l.index('DHLM')
|
||||
mresidx = l.index('MRES')
|
||||
addridx = l.index('ADDR')
|
||||
prefix = l[0]
|
||||
subLen = len(l)
|
||||
portIdx = l.index('PORT')
|
||||
elif subLen > 0:
|
||||
l = line.split(',')
|
||||
if subLen == len(l):
|
||||
motors.append((l[addridx],l[mresidx],l[lowlimidx],l[highlimidx]))
|
||||
port = l[portIdx]
|
||||
else:
|
||||
pass
|
||||
line = fin.readline()
|
||||
|
||||
print('%d motors found' % len(motors))
|
||||
|
||||
fname = os.path.splitext(sys.argv[1])[0] + '.cmd'
|
||||
|
||||
out = open(fname,'w')
|
||||
out.write('motorSimCreateController(\"%s\",%d)\n' % (port, len(motors)+2))
|
||||
for mot in motors:
|
||||
mres = float(mot[1])
|
||||
highlim = int(float(mot[3])/mres)
|
||||
lowlim = int(float(mot[2])/mres)
|
||||
out.write('motorSimConfigAxis(\"%s\",%s,%d,%d,0,0)\n' %(port,mot[0],highlim,lowlim))
|
||||
out.write('dbLoadTemplate(\"$(TOP)/%s\")\n' % (sys.argv[1]))
|
||||
out.close()
|
||||
print(fname + ' written')
|
213
utils/decodeMasterMACStatusR10.py
Executable file
213
utils/decodeMasterMACStatusR10.py
Executable file
@ -0,0 +1,213 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# List of tuples which encodes the states given in the file description.
|
||||
# Index first with the bit index, then with the bit value
|
||||
interpretation = [
|
||||
("Not ready to be switched on", "Ready to be switched on"), # Bit 0
|
||||
("Not switched on", "Switched on"), # Bit 1
|
||||
("Disabled", "Enabled"), # Bit 2
|
||||
("Ok", "Fault condition set"), # Bit 3
|
||||
("Motor supply voltage absent ", "Motor supply voltage present"), # Bit 4
|
||||
("Motor performs quick stop", "Ok"), # Bit 5
|
||||
("Switch on enabled", "Switch on disabled"), # Bit 6
|
||||
("Ok", "RWarning: Movement function was called while motor is still moving. The function call is ignored"), # Bit 7
|
||||
("Motor is idle", "Motor is currently moving"), # Bit 8
|
||||
("Motor does not execute command messages (local mode)", "Motor does execute command messages (remote mode)"), # Bit 9
|
||||
("Target not reached", "Target reached"), # Bit 10
|
||||
("Ok", "Internal limit active"), # Bit 11
|
||||
("Not specified", "Not specified"), # Bit 12
|
||||
("Not specified", "Not specified"), # Bit 13
|
||||
("No event set or event has not occurred yet", "Set event has occurred"), # Bit 14
|
||||
("Axis off (power disabled)", "Axis on (power enabled)"), # Bit 15
|
||||
]
|
||||
|
||||
def decode(value, big_endian: bool = False):
|
||||
|
||||
interpreted = []
|
||||
|
||||
bit_list = [(value >> shift_ind) & 1
|
||||
for shift_ind in range(value.bit_length())] # little endian
|
||||
|
||||
if big_endian:
|
||||
bit_list.reverse() # big endian
|
||||
|
||||
for (bit, interpretations) in zip(bit_list, interpretation):
|
||||
interpreted.append(interpretations[bit])
|
||||
return (bit_list, interpreted)
|
||||
|
||||
def print_decoded(bit_list, interpreted):
|
||||
for (idx, (bit_value, msg)) in enumerate(zip(bit_list, interpreted)):
|
||||
print(f"Bit {idx} = {bit_value}: {msg}")
|
||||
|
||||
def interactive():
|
||||
|
||||
# Imported here, because curses is not available in Windows. Using the
|
||||
# interactive mode therefore fails on Windows, but at least the single
|
||||
# command mode can be used (which would not be possible if we would import
|
||||
# curses at the top level)
|
||||
import curses
|
||||
|
||||
stdscr = curses.initscr()
|
||||
curses.noecho()
|
||||
curses.cbreak()
|
||||
stdscr.keypad(True)
|
||||
stdscr.scrollok(True)
|
||||
|
||||
stdscr.addstr(">> ")
|
||||
stdscr.refresh()
|
||||
|
||||
history = [""]
|
||||
ptr = len(history) - 1
|
||||
|
||||
while True:
|
||||
c = stdscr.getch()
|
||||
if c == curses.KEY_RIGHT:
|
||||
(y, x) = stdscr.getyx()
|
||||
if x < len(history[ptr]) + 3:
|
||||
stdscr.move(y, x+1)
|
||||
stdscr.refresh()
|
||||
elif c == curses.KEY_LEFT:
|
||||
(y, x) = stdscr.getyx()
|
||||
if x > 3:
|
||||
stdscr.move(y, x-1)
|
||||
stdscr.refresh()
|
||||
elif c == curses.KEY_UP:
|
||||
if ptr > 0:
|
||||
ptr -= 1
|
||||
stdscr.addch("\r")
|
||||
stdscr.clrtoeol()
|
||||
stdscr.addstr(">> " + history[ptr])
|
||||
elif c == curses.KEY_DOWN:
|
||||
if ptr < len(history) - 1:
|
||||
ptr += 1
|
||||
stdscr.addch("\r")
|
||||
stdscr.clrtoeol()
|
||||
stdscr.addstr(">> " + history[ptr])
|
||||
elif c == curses.KEY_ENTER or c == ord('\n') or c == ord('\r'):
|
||||
if history[ptr] == 'quit':
|
||||
break
|
||||
|
||||
# because of arrow keys move back to the end of the line
|
||||
(y, x) = stdscr.getyx()
|
||||
stdscr.move(y, 3+len(history[ptr]))
|
||||
|
||||
if history[ptr]:
|
||||
result = interpret_inputs(history[ptr].split())
|
||||
if result is None:
|
||||
stdscr.addstr(f"\nBAD INPUT: Expected input of 'value [big_endian]', where 'value' is an int or a float and 'big_endian' is an optional boolean argument.")
|
||||
else:
|
||||
(arg, big_endian) = result
|
||||
(bit_list, interpreted) = decode(arg, big_endian)
|
||||
for (idx, (bit_value, msg)) in enumerate(zip(bit_list, interpreted)):
|
||||
stdscr.addstr(f"\nBit {idx} = {bit_value}: {msg}")
|
||||
stdscr.refresh()
|
||||
|
||||
if ptr == len(history) - 1 and history[ptr] != "":
|
||||
history += [""]
|
||||
else:
|
||||
history[-1] = ""
|
||||
ptr = len(history) - 1
|
||||
|
||||
stdscr.addstr("\n>> ")
|
||||
stdscr.refresh()
|
||||
|
||||
else:
|
||||
if ptr < len(history) - 1: # Modifying previous input
|
||||
if len(history[-1]) == 0:
|
||||
history[-1] = history[ptr]
|
||||
ptr = len(history) - 1
|
||||
|
||||
else:
|
||||
history += [history[ptr]]
|
||||
ptr = len(history) - 1
|
||||
|
||||
if c == curses.KEY_BACKSPACE:
|
||||
if len(history[ptr]) == 0:
|
||||
continue
|
||||
(y, x) = stdscr.getyx()
|
||||
history[ptr] = history[ptr][0:x-4] + history[ptr][x-3:]
|
||||
stdscr.addch("\r")
|
||||
stdscr.clrtoeol()
|
||||
stdscr.addstr(">> " + history[ptr])
|
||||
stdscr.move(y, x-1)
|
||||
stdscr.refresh()
|
||||
|
||||
else:
|
||||
(y, x) = stdscr.getyx()
|
||||
history[ptr] = history[ptr][0:x-3] + chr(c) + history[ptr][x-3:]
|
||||
stdscr.addch("\r")
|
||||
stdscr.clrtoeol()
|
||||
stdscr.addstr(">> " + history[ptr])
|
||||
stdscr.move(y, x+1)
|
||||
stdscr.refresh()
|
||||
|
||||
# to quit
|
||||
curses.nocbreak()
|
||||
stdscr.keypad(False)
|
||||
curses.echo()
|
||||
curses.endwin()
|
||||
|
||||
def interpret_inputs(inputs):
|
||||
|
||||
number = None
|
||||
big_endian = False
|
||||
try:
|
||||
number = int(float(inputs[0]))
|
||||
if len(inputs) > 1:
|
||||
second_arg = inputs[1]
|
||||
if second_arg == "True" or second_arg == "true":
|
||||
big_endian = True
|
||||
elif second_arg == "False" or second_arg == "false":
|
||||
big_endian = False
|
||||
else:
|
||||
big_endian = bool(int(second_arg))
|
||||
return (number, big_endian)
|
||||
except:
|
||||
return None
|
||||
|
||||
if __name__ == "__main__":
|
||||
from sys import argv
|
||||
|
||||
if len(argv) == 1:
|
||||
# Start interactive mode
|
||||
interactive()
|
||||
else:
|
||||
|
||||
result = interpret_inputs(argv[1:])
|
||||
|
||||
if result is None:
|
||||
print("""
|
||||
Decode R10 message of MasterMACs
|
||||
------------------
|
||||
|
||||
MasterMACs returns its status message (R10) as a floating-point number.
|
||||
The bits of this float encode different states. These states are stored
|
||||
in the interpretation variable.
|
||||
|
||||
This script can be used in two different ways:
|
||||
|
||||
Option 1: Single Command
|
||||
------------------------
|
||||
|
||||
Usage: decodeMasterMACStatusR10.py value [big_endian]
|
||||
|
||||
'value' is the return value of a R10 command. This value is interpreted
|
||||
bit-wise and the result is printed out. The optional second argument can
|
||||
be used to specify whether the input value needs to be interpreted as
|
||||
little or big endian. Default is False.
|
||||
|
||||
Option 2: CLI Mode
|
||||
------------------
|
||||
|
||||
Usage: decodeMasterMACStatusR10.py
|
||||
|
||||
A prompt will be opened. Type in the return value of a R10 command, hit
|
||||
enter and the interpretation will be printed in the prompt. After that,
|
||||
the next value can be typed in. Type 'quit' to close the prompt.
|
||||
""")
|
||||
else:
|
||||
print("Motor status")
|
||||
print("============")
|
||||
(arg, big_endian) = result
|
||||
(bit_list, interpreted) = decode(arg, big_endian)
|
||||
print_decoded(bit_list, interpreted)
|
178
utils/deltatau.py
Executable file
178
utils/deltatau.py
Executable file
@ -0,0 +1,178 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
This is some code for communicating with a Delta-Tau PMAC motor
|
||||
controller as used at SINQ from python. Python 3 is assumed and
|
||||
tested. We use the ethernet protocol of the Pmac. This code benefits
|
||||
from the SICS driver for Pmac which in turn benefits from the driver
|
||||
written by Pete Leicester at Diamond.
|
||||
|
||||
|
||||
Usage: python36 deltatau.py pmachost:port command
|
||||
Then returns the response for command.
|
||||
|
||||
Mark Koennecke, October 2019
|
||||
"""
|
||||
|
||||
import struct
|
||||
import socket
|
||||
import curses
|
||||
|
||||
def packPmacCommand(command):
|
||||
# 0x40 = VR_DOWNLOAD
|
||||
# 0xBF = VR_PMAC_GETRESPONSE
|
||||
buf = struct.pack('BBHHH',0x40,0xBF,0,0,socket.htons(len(command)))
|
||||
buf = buf + bytes(command,'utf-8')
|
||||
return buf
|
||||
|
||||
def readPmacReply(input):
|
||||
msg = bytearray()
|
||||
expectAck = True
|
||||
while True:
|
||||
b = input.recv(1)
|
||||
bint = int.from_bytes(b,byteorder='little')
|
||||
if bint == 2 or bint == 7: #STX or BELL
|
||||
expectAck = False
|
||||
continue
|
||||
if expectAck and bint == 6: # ACK
|
||||
return bytes(msg)
|
||||
else:
|
||||
if bint == 13 and not expectAck: # CR
|
||||
return bytes(msg)
|
||||
else:
|
||||
msg.append(bint)
|
||||
|
||||
if __name__ == "__main__":
|
||||
from sys import argv
|
||||
|
||||
try:
|
||||
|
||||
addr = argv[1].split(':')
|
||||
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
|
||||
s.connect((addr[0],int(addr[1])))
|
||||
|
||||
if len(argv) == 3:
|
||||
buf = packPmacCommand(argv[2])
|
||||
s.send(buf)
|
||||
reply = readPmacReply(s)
|
||||
print(reply.decode('utf-8') + '\n')
|
||||
|
||||
else:
|
||||
|
||||
try:
|
||||
|
||||
stdscr = curses.initscr()
|
||||
curses.noecho()
|
||||
curses.cbreak()
|
||||
stdscr.keypad(True)
|
||||
stdscr.scrollok(True)
|
||||
|
||||
stdscr.addstr(">> ")
|
||||
stdscr.refresh()
|
||||
|
||||
history = [""]
|
||||
ptr = len(history) - 1
|
||||
|
||||
while True:
|
||||
c = stdscr.getch()
|
||||
if c == curses.KEY_RIGHT:
|
||||
(y, x) = stdscr.getyx()
|
||||
if x < len(history[ptr]) + 3:
|
||||
stdscr.move(y, x+1)
|
||||
stdscr.refresh()
|
||||
elif c == curses.KEY_LEFT:
|
||||
(y, x) = stdscr.getyx()
|
||||
if x > 3:
|
||||
stdscr.move(y, x-1)
|
||||
stdscr.refresh()
|
||||
elif c == curses.KEY_UP:
|
||||
if ptr > 0:
|
||||
ptr -= 1
|
||||
stdscr.addch("\r")
|
||||
stdscr.clrtoeol()
|
||||
stdscr.addstr(">> " + history[ptr])
|
||||
elif c == curses.KEY_DOWN:
|
||||
if ptr < len(history) - 1:
|
||||
ptr += 1
|
||||
stdscr.addch("\r")
|
||||
stdscr.clrtoeol()
|
||||
stdscr.addstr(">> " + history[ptr])
|
||||
elif c == curses.KEY_ENTER or c == ord('\n') or c == ord('\r'):
|
||||
if history[ptr] == 'quit':
|
||||
break
|
||||
|
||||
# because of arrow keys move back to the end of the line
|
||||
(y, x) = stdscr.getyx()
|
||||
stdscr.move(y, 3+len(history[ptr]))
|
||||
|
||||
if history[ptr]:
|
||||
buf = packPmacCommand(history[ptr])
|
||||
s.send(buf)
|
||||
reply = readPmacReply(s)
|
||||
stdscr.addstr("\n" + reply.decode('utf-8')[0:-1])
|
||||
|
||||
if ptr == len(history) - 1 and history[ptr] != "":
|
||||
history += [""]
|
||||
else:
|
||||
history[-1] = ""
|
||||
ptr = len(history) - 1
|
||||
|
||||
stdscr.addstr("\n>> ")
|
||||
stdscr.refresh()
|
||||
|
||||
else:
|
||||
if ptr < len(history) - 1: # Modifying previous input
|
||||
if len(history[-1]) == 0:
|
||||
history[-1] = history[ptr]
|
||||
ptr = len(history) - 1
|
||||
|
||||
else:
|
||||
history += [history[ptr]]
|
||||
ptr = len(history) - 1
|
||||
|
||||
if c == curses.KEY_BACKSPACE:
|
||||
if len(history[ptr]) == 0:
|
||||
continue
|
||||
(y, x) = stdscr.getyx()
|
||||
history[ptr] = history[ptr][0:x-4] + history[ptr][x-3:]
|
||||
stdscr.addch("\r")
|
||||
stdscr.clrtoeol()
|
||||
stdscr.addstr(">> " + history[ptr])
|
||||
stdscr.move(y, x-1)
|
||||
stdscr.refresh()
|
||||
|
||||
else:
|
||||
(y, x) = stdscr.getyx()
|
||||
history[ptr] = history[ptr][0:x-3] + chr(c) + history[ptr][x-3:]
|
||||
stdscr.addch("\r")
|
||||
stdscr.clrtoeol()
|
||||
stdscr.addstr(">> " + history[ptr])
|
||||
stdscr.move(y, x+1)
|
||||
stdscr.refresh()
|
||||
|
||||
finally:
|
||||
|
||||
# to quit
|
||||
curses.nocbreak()
|
||||
stdscr.keypad(False)
|
||||
curses.echo()
|
||||
curses.endwin()
|
||||
|
||||
except:
|
||||
print("""
|
||||
Invalid Arguments
|
||||
|
||||
Option 1: Single Command
|
||||
------------------------
|
||||
|
||||
Usage: deltatau.py pmachost:port command
|
||||
This then returns the response for command.
|
||||
|
||||
Option 2: CLI Mode
|
||||
------------------
|
||||
|
||||
Usage: deltatau.py pmachost:port
|
||||
|
||||
You can then type in a command, hit enter, and the response will see
|
||||
the reponse, before being prompted to again enter a command. Type
|
||||
'quit' to close prompt.
|
||||
""")
|
54
utils/macmaster.py
Executable file
54
utils/macmaster.py
Executable file
@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Usage: macmaster.py macmasterhost port
|
||||
Listen for commands to send and returns reponse
|
||||
until exit has been typed
|
||||
|
||||
Mark Koennecke, March 2023
|
||||
"""
|
||||
|
||||
import socket
|
||||
import struct
|
||||
|
||||
|
||||
class MasterMACS():
|
||||
def __init__(self, host, port):
|
||||
self._socke = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self._socke.connect((host, int(port)))
|
||||
|
||||
def send(self, command):
|
||||
buf = struct.pack('BBBB', 0x05, len(command) + 4, 0, 0x19)
|
||||
buf += bytes(command, 'utf-8')
|
||||
buf += struct.pack('BB', 0x0D, 0x03)
|
||||
self._socke.send(buf)
|
||||
|
||||
def receive(self):
|
||||
buf = self._socke.recv(35, socket.MSG_WAITALL)
|
||||
if len(buf) < 35:
|
||||
raise EOFException('Master MACS returned only %d bytes, 35 expected' % len(buf))
|
||||
idx = buf.find(0x0D)
|
||||
ackbyte = buf[idx-1]
|
||||
if ackbyte == 0x06:
|
||||
ack = 'ACK'
|
||||
elif ackbyte == 0x15:
|
||||
ack = 'NAK',
|
||||
else:
|
||||
ack = 'NO'
|
||||
reply = buf[4:idx-1].decode('utf-8')
|
||||
return ack, reply
|
||||
|
||||
if __name__ == "__main__":
|
||||
from sys import argv, exit, stdin
|
||||
if len(argv) < 3:
|
||||
print('Usage:\n\tmacmaster.py machost macport')
|
||||
exit(1)
|
||||
mac = MasterMACS(argv[1], argv[2])
|
||||
while(True):
|
||||
# import pdb; pdb.set_trace()
|
||||
line = stdin.readline()
|
||||
if line.find('exit') >= 0:
|
||||
exit(0)
|
||||
mac.send(line.strip())
|
||||
ack, reply = mac.receive()
|
||||
print('%s, %s' %(ack, reply))
|
||||
|
58
utils/physhell.tcl
Executable file
58
utils/physhell.tcl
Executable file
@ -0,0 +1,58 @@
|
||||
#!/usr/bin/tclsh
|
||||
# This is a little program which acts as a shell to the phytron.
|
||||
# It connects to another program which is supposed to talk to the phytron controller
|
||||
# It reads from stdin, packages the message into the phytron format and sends it to
|
||||
# the phytron communication program. Then it reads from the phytron communication program
|
||||
# and unpacks the reply.
|
||||
#
|
||||
# This is also a nice exampe for dealing with binary in Tcl
|
||||
# Making the binary character only worked with %c
|
||||
# The only way to do the comparison is with the string comare
|
||||
#
|
||||
# Mark Könnecke, September 2016
|
||||
|
||||
|
||||
if {[llength $argv] < 1} {
|
||||
puts stdout "Usage:\n\t physhell.tcl phytronprogram"
|
||||
exit
|
||||
}
|
||||
|
||||
set phprogram [lindex $argv 0]
|
||||
|
||||
set phyio [open "| $phprogram" "w+b"]
|
||||
fconfigure $phyio -buffering none
|
||||
fconfigure $phyio -translation {binary binary}
|
||||
set etx [format "%c" 0x03]
|
||||
set stx [format "%c" 0x02]
|
||||
set ack [format "%c" 0x06]
|
||||
set nack [format "%c" 0x05]
|
||||
|
||||
|
||||
while {1} {
|
||||
set inp [gets stdin]
|
||||
puts -nonewline $phyio [format "%c%s%c" 0x02 $inp 0x03]
|
||||
set mode start
|
||||
set reply ""
|
||||
while {[string compare $mode done] != 0 } {
|
||||
set c [read $phyio 1]
|
||||
switch $mode {
|
||||
start {
|
||||
if {[string compare $c $stx] == 0} {
|
||||
set mode data
|
||||
}
|
||||
}
|
||||
data {
|
||||
if {[string compare $c $etx] == 0} {
|
||||
puts stdout $reply
|
||||
set mode done
|
||||
} elseif {[string compare $c $nack] == 0} {
|
||||
append reply NACK
|
||||
} elseif {[string compare $c $ack] == 0} {
|
||||
append reply ACK
|
||||
} else {
|
||||
append reply $c
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
114
utils/syncEL734Sub.py
Executable file
114
utils/syncEL734Sub.py
Executable file
@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
This little program synchronizes an EPICS substitutions file with values read
|
||||
from the EL734 motor controller. Some research showed that the EPICS motorRecord
|
||||
seems to have no means of reading configuration data like limits or speeds or such
|
||||
from the device support. Than it is the easiest to read the data with this small
|
||||
program and update the substitutions file with it.
|
||||
|
||||
Mark Koennecke, January 2023
|
||||
"""
|
||||
|
||||
import socket
|
||||
import sys
|
||||
import time
|
||||
|
||||
socke = None
|
||||
sockfd = None
|
||||
|
||||
# --------------- communication section
|
||||
def connect(host, port):
|
||||
global socke, sockfd
|
||||
socke = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
# socke.connect(('localhost', 5050))
|
||||
socke.connect((host, port))
|
||||
socke.settimeout(2.)
|
||||
sockfd = socke.makefile(mode='rw', newline='\r')
|
||||
sockfd.write('RMT 1\r')
|
||||
sockfd.flush()
|
||||
sockfd.write('ECHO 0\r')
|
||||
sockfd.flush()
|
||||
sockfd.readline()
|
||||
|
||||
def transact(command):
|
||||
global socke, sockfd
|
||||
sockfd.write(command + '\r')
|
||||
sockfd.flush()
|
||||
time.sleep(.1)
|
||||
return sockfd.readline()
|
||||
|
||||
def find_commas(rawline):
|
||||
comma_list = []
|
||||
for i in range(len(rawline)):
|
||||
if rawline[i] == ',':
|
||||
comma_list.append(i)
|
||||
return comma_list
|
||||
|
||||
def pretty_line(newlist, comma_list):
|
||||
"""
|
||||
adds spaces to match the tabbing of the pattern line
|
||||
"""
|
||||
newline = ''
|
||||
for item, idx in zip(newlist, comma_list):
|
||||
length = len(newline) + 1 + len(item)
|
||||
if length < idx:
|
||||
newline += ' ' * (idx - length)
|
||||
newline += item
|
||||
newline += ','
|
||||
newline += newlist[-1]
|
||||
return newline[0:-1]
|
||||
|
||||
def fix_line(par_list, index_list):
|
||||
# import pdb; pdb.set_trace()
|
||||
addridx = index_list.index('ADDR')
|
||||
motNo = par_list[addridx]
|
||||
limits = transact('H ' + motNo)
|
||||
time.sleep(.1)
|
||||
l = limits.split()
|
||||
lowidx = index_list.index('DLLM')
|
||||
# import pdb; pdb.set_trace()
|
||||
par_list[lowidx] = l[0]
|
||||
highidx = index_list.index('DHLM')
|
||||
par_list[highidx] = l[1]
|
||||
speed = transact('J ' + motNo)
|
||||
vidx = index_list.index('VELO')
|
||||
par_list[vidx] = speed.strip()
|
||||
# vidx = index_list.index('VMAX')
|
||||
# par_list[vidx] = speed.strip()
|
||||
return par_list
|
||||
|
||||
def scan_substitution_file(filename):
|
||||
index_list = None
|
||||
# import pdb; pdb.set_trace()
|
||||
with open(filename, 'r') as fin:
|
||||
rawline = fin.readline()
|
||||
while rawline:
|
||||
line = rawline.replace(' ','')
|
||||
line = line.strip('{}')
|
||||
l = line.split(',')
|
||||
if line.find('DHLM') > 0:
|
||||
index_list = l
|
||||
comma_list = find_commas(rawline)
|
||||
sys.stdout.write(rawline)
|
||||
elif not index_list:
|
||||
sys.stdout.write(rawline)
|
||||
else:
|
||||
if len(l) == len(index_list):
|
||||
newlist = fix_line(l, index_list)
|
||||
# newline = ','.join(newlist)
|
||||
newline = pretty_line(newlist, comma_list)
|
||||
sys.stdout.write('{' + newline + '\n')
|
||||
else:
|
||||
sys.stdout.write(rawline)
|
||||
rawline = fin.readline()
|
||||
|
||||
#------------------ main
|
||||
if len(sys.argv) < 4:
|
||||
print('Usage:\n\tsyncEL734sub.py <host> <port> <substitutions-file>\n')
|
||||
sys.exit(1)
|
||||
|
||||
connect(sys.argv[1], int(sys.argv[2]))
|
||||
scan_substitution_file(sys.argv[3])
|
||||
socke.close()
|
||||
|
||||
|
98
utils/syncMasterMAC.py
Executable file
98
utils/syncMasterMAC.py
Executable file
@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
This little program synchronizes an EPICS substitutions file with values read
|
||||
from a MasterMAC motor controller.
|
||||
|
||||
Mark Koennecke, February 2023
|
||||
"""
|
||||
import sys
|
||||
import time
|
||||
|
||||
from macmaster import MasterMACS
|
||||
|
||||
def find_commas(rawline):
|
||||
comma_list = []
|
||||
for i in range(len(rawline)):
|
||||
if rawline[i] == ',':
|
||||
comma_list.append(i)
|
||||
return comma_list
|
||||
|
||||
def pretty_line(newlist, comma_list):
|
||||
"""
|
||||
adds spaces to match the tabbing of the patter line
|
||||
"""
|
||||
newline = ''
|
||||
for item, idx in zip(newlist, comma_list):
|
||||
length = len(newline) + 1 + len(item)
|
||||
if length < idx:
|
||||
newline += ' ' * (idx - length)
|
||||
newline += item
|
||||
newline += ','
|
||||
newline += newlist[-1]
|
||||
return newline[0:-1]
|
||||
|
||||
def transact(command):
|
||||
global mac
|
||||
mac.send(command)
|
||||
time.sleep(.1)
|
||||
return mac.receive()
|
||||
|
||||
def fix_line(par_list, index_list):
|
||||
# import pdb; pdb.set_trace()
|
||||
addridx = index_list.index('ADDR')
|
||||
motNo = int(par_list[addridx])
|
||||
ack, reply = transact('%dR24' % motNo)
|
||||
if ack == 'NO':
|
||||
idx = reply.find('=')
|
||||
lowlim = reply[idx+1:]
|
||||
lowidx = index_list.index('DLLM')
|
||||
par_list[lowidx] = lowlim.strip()
|
||||
ack, reply = transact('%dR23' % motNo)
|
||||
if ack == 'NO':
|
||||
idx = reply.find('=')
|
||||
highlim = reply[idx+1:]
|
||||
highidx = index_list.index('DHLM')
|
||||
par_list[highidx] = highlim.strip()
|
||||
ack, reply = transact('%dR05' % motNo)
|
||||
if ack == 'NO':
|
||||
idx = reply.find('=')
|
||||
speed = reply[idx+1:]
|
||||
speedidx = index_list.index('VELO')
|
||||
par_list[speedidx] = speed.strip()
|
||||
return par_list
|
||||
|
||||
def scan_substitution_file(filename):
|
||||
index_list = None
|
||||
# import pdb; pdb.set_trace()
|
||||
with open(filename, 'r') as fin:
|
||||
rawline = fin.readline()
|
||||
# import pdb; pdb.set_trace()
|
||||
while rawline:
|
||||
line = rawline.replace(' ','')
|
||||
line = line.strip('{}')
|
||||
l = line.split(',')
|
||||
if line.find('DHLM') > 0:
|
||||
index_list = l
|
||||
comma_list = find_commas(rawline)
|
||||
sys.stdout.write(rawline)
|
||||
elif not index_list:
|
||||
sys.stdout.write(rawline)
|
||||
else:
|
||||
if len(l) == len(index_list):
|
||||
newlist = fix_line(l, index_list)
|
||||
# newline = ','.join(newlist)
|
||||
newline = pretty_line(newlist, comma_list)
|
||||
sys.stdout.write('{' + newline + '\n')
|
||||
else:
|
||||
sys.stdout.write(rawline)
|
||||
rawline = fin.readline()
|
||||
|
||||
#------------------ main
|
||||
if len(sys.argv) < 4:
|
||||
print('Usage:\n\tsyncMMACSub.py <host> <port> <substitutions-file>\n')
|
||||
sys.exit(1)
|
||||
|
||||
mac = MasterMACS(sys.argv[1], sys.argv[2])
|
||||
scan_substitution_file(sys.argv[3])
|
||||
|
||||
|
104
utils/syncPmacSub.py
Executable file
104
utils/syncPmacSub.py
Executable file
@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
This little program synchronizes an EPICS substitutions file with values read
|
||||
from the delta tau motor controller. Some research showed that the EPICS motorRecord
|
||||
seems to have no means of reading configuration data like limits or speeds or such
|
||||
from the device support. Than it is the easiest to read the data with this small
|
||||
program and update the substitutions file with it.
|
||||
|
||||
Mark Koennecke, January 2023
|
||||
"""
|
||||
|
||||
import socket
|
||||
import sys
|
||||
from deltatau import packPmacCommand, readPmacReply
|
||||
|
||||
|
||||
socke = None
|
||||
|
||||
# --------------- communication section
|
||||
def connect(host, port):
|
||||
global socke, sockfd
|
||||
socke = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
# socke.connect(('localhost', 5050))
|
||||
socke.connect((host, port))
|
||||
socke.settimeout(1.)
|
||||
|
||||
def transact(command):
|
||||
global socke
|
||||
data = packPmacCommand(command)
|
||||
socke.send(data)
|
||||
reply = readPmacReply(socke)
|
||||
return reply.decode('utf-8')
|
||||
|
||||
def find_commas(rawline):
|
||||
comma_list = []
|
||||
for i in range(len(rawline)):
|
||||
if rawline[i] == ',':
|
||||
comma_list.append(i)
|
||||
return comma_list
|
||||
|
||||
def pretty_line(newlist, comma_list):
|
||||
"""
|
||||
adds spaces to match the tabbing of the patter line
|
||||
"""
|
||||
newline = ''
|
||||
for item, idx in zip(newlist, comma_list):
|
||||
length = len(newline) + 1 + len(item)
|
||||
if length < idx:
|
||||
newline += ' ' * (idx - length)
|
||||
newline += item
|
||||
newline += ','
|
||||
newline += newlist[-1]
|
||||
return newline[0:-1]
|
||||
|
||||
def fix_line(par_list, index_list):
|
||||
# import pdb; pdb.set_trace()
|
||||
addridx = index_list.index('ADDR')
|
||||
motNo = int(par_list[addridx])
|
||||
lowlim = transact('Q%d14' % motNo)
|
||||
lowidx = index_list.index('DLLM')
|
||||
par_list[lowidx] = lowlim.strip()
|
||||
highlim = transact('Q%d13' % motNo)
|
||||
highidx = index_list.index('DHLM')
|
||||
par_list[highidx] = highlim.strip()
|
||||
speed = transact('Q%d03' % motNo)
|
||||
speedidx = index_list.index('VELO')
|
||||
par_list[speedidx] = speed.strip()
|
||||
return par_list
|
||||
|
||||
def scan_substitution_file(filename):
|
||||
index_list = None
|
||||
# import pdb; pdb.set_trace()
|
||||
with open(filename, 'r') as fin:
|
||||
rawline = fin.readline()
|
||||
while rawline:
|
||||
line = rawline.replace(' ','')
|
||||
line = line.strip('{}')
|
||||
l = line.split(',')
|
||||
if line.find('DHLM') > 0:
|
||||
index_list = l
|
||||
comma_list = find_commas(rawline)
|
||||
sys.stdout.write(rawline)
|
||||
elif not index_list:
|
||||
sys.stdout.write(rawline)
|
||||
else:
|
||||
if len(l) == len(index_list):
|
||||
newlist = fix_line(l, index_list)
|
||||
# newline = ','.join(newlist)
|
||||
newline = pretty_line(newlist, comma_list)
|
||||
sys.stdout.write('{' + newline + '\n')
|
||||
else:
|
||||
sys.stdout.write(rawline)
|
||||
rawline = fin.readline()
|
||||
|
||||
#------------------ main
|
||||
if len(sys.argv) < 4:
|
||||
print('Usage:\n\tsyncPmacSub.py <host> <port> <substitutions-file>\n')
|
||||
sys.exit(1)
|
||||
|
||||
connect(sys.argv[1], int(sys.argv[2]))
|
||||
scan_substitution_file(sys.argv[3])
|
||||
socke.close()
|
||||
|
||||
|
Reference in New Issue
Block a user