Start from scope plugin

This commit is contained in:
2024-03-01 15:07:38 +01:00
commit a41e8c69b2
15 changed files with 2097 additions and 0 deletions

16
.gitignore vendored Normal file
View File

@@ -0,0 +1,16 @@
*~
*-dev
modules.order
Module.symvers
Mkfile.old
core.*
#*
.#*
\#*
*.local
\#*
.cvsignore
*_old/
*PVs.list
*-loc/*.Makefile
ecmc_plugin_scope/*.Makefile

47
GNUmakefile Normal file
View File

@@ -0,0 +1,47 @@
include /ioc/tools/driver.makefile
MODULE = ecmc_plugin_daq
BUILDCLASSES = Linux
ARCH_FILTER = deb10%
# Run 7.0.6 for now
EXCLUDE_VERSIONS+=3 7.0.5 7.0.6
IGNORE_MODULES += asynMotor
IGNORE_MODULES += motorBase
USR_CXXFLAGS += -std=c++17
OPT_CXXFLAGS_YES = -O3
# dependencies
ECmasterECMC_VERSION = v1.1.0
# motorECMC_VERSION = 7.0.7-ESS
#ecmc_VERSION = v9.0.1_RC4
ecmc_VERSION = 9.1.0
################################################################################
# THIS RELATES TO THE EtherCAT MASTER LIBRARY
# IT IS OF PARAMOUNT IMPORTANCE TO LOAD THE PROPER KERNEL MODULE
# ################################################################################
#USR_LDFLAGS += -lethercat
#
#EC_MASTER_LIB = ${EPICS_MODULES}/ECmasterECMC/${ECmasterECMC_VERSION}/R${EPICSVERSION}/lib/${T_A}
#USR_LDFLAGS += -Wl,-rpath=${EC_MASTER_LIB}
#USR_LDFLAGS += -L ${EC_MASTER_LIB}
#
#BASE_DIR = .
#SRC_DIR = $(BASE_DIR)/src
#DB_DIR = $(BASE_DIR)/Db
#
#SOURCES += $(SRC_DIR)/ecmcPluginSafety.c
#SOURCES += $(SRC_DIR)/ecmcSafetyPlgWrap.cpp
#SOURCES += $(SRC_DIR)/ecmcSS1SafetyGroup.cpp
#
##SOURCES += $(foreach d,${SRC_DIR}, $(wildcard $d/*.c) $(wildcard $d/*.cpp))
#HEADERS += $(foreach d,${SRC_DIR}, $(wildcard $d/*.h))
#DBDS += $(foreach d,${SRC_DIR}, $(wildcard $d/*.dbd))
#SCRIPTS += $(BASE_DIR)/startup.cmd
#SCRIPTS += $(BASE_DIR)/scripts/addSS1Group.cmd
#SCRIPTS += $(BASE_DIR)/scripts/addAxisToSafetyGroup.cmd
#TEMPLATES += $(wildcard $(DB_DIR)/*.template)

387
README.md Normal file
View File

@@ -0,0 +1,387 @@
e3-ecmcPlugin_Scope
======
ESS Site-specific EPICS module : ecmcPlugin_Scope
A shared library with Scope functionalities loadable into ecmc:
https://github.com/epics-modules/ecmc (or local ess fork https://github.com/icshwi/ecmc).
Configuration is made through ecmccfg:
https://github.com/paulscherrerinstitute/ecmccfg (ot local ess fork https://github.com/icshwi/ecmccfg)
# Introduction
The main functionality of this plugin is triggerd sampling of ethercat data from oversampled and timestamped ethercat slaves, like:
Data:
* EL3702
* EL3742
* EL5101-0011
* ELM3604
* ....
Trigger:
* EL1252
* EL1252-0050
## Loading of Scope plugin in ecmc:
NOTE: "require" can be used to get access to the template file. However, the plugin also needs to be loaded like below.
A plugin is loaded by the ecmccfg command loadPlugin:
https://github.com/icshwi/ecmccfg/blob/master/scripts/loadPlugin.cmd
Example:
```
epicsEnvSet(RESULT_NELM, 1024)
epicsEnvSet(ECMC_PLUGIN_FILNAME,"/home/pi/sources/e3-ecmcPlugin_Scope/ecmcPlugin_Scope-loc/O.7.0.4_linux-arm/libecmcPlugin_Scope.so")
epicsEnvSet(ECMC_PLUGIN_CONFIG,"SOURCE=ec0.s${SLAVE_NUM_AI}.mm.CH1_ARRAY;DBG_PRINT=1;TRIGG=ec0.s${SLAVE_NUM_TRIGG}.CH1_LATCH_POS;SOURCE_NEXTTIME=ec0.s${SLAVE_NUM_AI}.NEXT_TIME;RESULT_ELEMENTS=${RESULT_NELM};")
${SCRIPTEXEC} ${ecmccfg_DIR}loadPlugin.cmd, "PLUGIN_ID=1,FILE=${ECMC_PLUGIN_FILNAME},CONFIG='${ECMC_PLUGIN_CONFIG}', REPORT=1"
epicsEnvUnset(ECMC_PLUGIN_FILNAME)
epicsEnvUnset(ECMC_PLUGIN_CONFIG)
dbLoadRecords("../template/ecmcPluginScope.template","P=$(IOC):,PORT=${ECMC_ASYN_PORT},INDEX=0,RESULT_NELM=${RESULT_NELM},RESULT_DTYP=asynInt16ArrayIn,RESULT_FTVL=SHORT")
epicsEnvUnset(RESULT_NELM, 1024)
```
This plugin supports multiple loading. For each load of the plugin a new Scope object will be created. In order to access these plugins, from plc:s or EPICS records, they can be accessed by an index. The first Scope plugin will have index 0. The next loaded Scope plugin will have index 1...
Note: If another plugin is loaded in between the loading of Scope plugins, it will have no affect on these Scope indexes (so the Scope index is _not_ the same as plugin index).
## Configuration:
Three links to ethercat data needs to be defined:
* Source data
* Source data timestamp
* Trigger timestamp
All these three links needs to be defined in the plugin startup configuration string.
Other configurations that can be made:
* Data elements to collect
* Debug printouts
* Enable
### Source data (mandatory)
The source data should normally be a ecmc memmap.
The source is defined by the "SOURCE" configuration string:
```
SOURCE=ec0.s2.mm.CH1_ARRAY;
```
### Source data timestamp (mandatory)
In order to know which source data elements that correspond to the trigger value, the oversampled ethercat slaves normally have a pdo that contains the value of the next dc sync time which is the dc timestamp of the next acquired data element.
The source timestamp is defined by the "SOURCE_NEXTTIME" configuration string:
```
SOURCE_NEXTTIME=ec0.s2.NEXT_TIME;
```
This timestamp can be either in 32bit or 64bit format. The "NEXT_TIME" is always considered to be later than the trigger timestamp.
### Trigger (mandatory)
The trigger should normally be a timestamped digital input, like EL1252.
The trigger timestamp is defined by the "TRIGG" configuration string:
```
TRIGG=ec0.s5.CH1_LATCH_POS;
```
This timestamp can be either in 32bit or 64bit format. If 32 bits then "NEXT_TIME" is always considered to be later than the trigger timestamp.
### Data elements to collect (optional)
The number of values to be collected after the trigger is defined by setting the option "RESULT_ELEMENTS" in the configurations string. The default value is 1024 data elements of the same type as the choosen source.
```
RESULT_ELEMENTS=2048;
```
### Debug printouts (optional)
Debug printouts can be enbaled/disabled by the option DBG_PRINT (defaults to 0)
```
DBG_PRINT=1;
```
### Enable (optional)
The acuiring of data can be enabled/disabled by the "ENABLE" option (defaults to 1)
```
ENABLE=0;
```
### Example of complete configuration string
```
epicsEnvSet(ECMC_PLUGIN_CONFIG,"SOURCE=ec0.s${SLAVE_NUM_AI}.mm.CH1_ARRAY;DBG_PRINT=1;TRIGG=ec0.s${SLAVE_NUM_TRIGG}.CH1_LATCH_POS;SOURCE_NEXTTIME=ec0.s${SLAVE_NUM_AI}.NEXT_TIME;RESULT_ELEMENTS=${RESULT_NELM};")
```
## EPICS records
Each Scope plugin object will create a new asyn parameters.
The reason for a dedicated asynport is to disturb ecmc as little as possible.
The plugin contains a template file, "ecmcPluginScope.template", that will make most information availbe from records:
* Enable (rw)
* Data Source (ro)
* Trigger source (ro)
* Resultdata (ro)
The available records from this template file can be listed by the cmd:
```
raspberrypi-15269 > dbgrep *Scope*
IOC_TEST:Plugin-Scope0-MissTriggCntAct
IOC_TEST:Plugin-Scope0-ScanToTriggSamples
IOC_TEST:Plugin-Scope0-TriggCntAct
IOC_TEST:Plugin-Scope0-Enable
IOC_TEST:Plugin-Scope0-DataSource
IOC_TEST:Plugin-Scope0-TriggSource
IOC_TEST:Plugin-Scope0-NextTimeSource
IOC_TEST:Plugin-Scope0-Data-Act
```
## GUI
### ecmcScopeMainGui
A simple pyqt gui is provided to visualize and control the scope ([GUI](tools/ecmcScopeMainGui.py)).
The gui are included in the ecmccomgui repo:
https://github.com/anderssandstrom/ecmccomgui
The tool recives data from the EPICS records by pyepics framework.
Help screen of ecmcScopeMainGui.py
```
$ python ecmcScopeMainGui.py
ecmcScopeMainGui: Plots waveforms of FFT data (updates on Y data callback).
python ecmcScopeMainGui.py <prefix> <fftId>
<prefix> : Ioc prefix ('IOC_TEST:')
<scopeId> : Id of scope plugin ('0')
example : python ecmcScopeMainGui.py 'IOC_TEST:' '0'
Will connect to Pvs: <prefix>Plugin-Scope<scopeId>-*
```
Example: Start ecmcFFMainTGui.py for:
* predix="IOC_TEST:"
* scopePluginId=0 (the first loaded Scope plugin in the ioc)
```
python ecmcScopeMainGui.py 'IOC_TEST:' '0'
```
![gui.py](docs/gui.png)
### Needed packages:
* python 3.5
* epics
* PyQt5
* numpy
* matplotlib
## Example script
An example script can be found in the iocsh directory of this repo.
The example is based on:
* EL3702 for acquiring analog data with a oversampling factor of 100 (100kHz sampling rate)
* EL1252 for timestamped trigger
The below image shows triggered acquisition in 10Hz of a 100Hz sinus 1Vpp. For each trigger, 500 samples are acquired in 100kHz sample rate and pushed to epics as waveforms (in 10Hz with NELM 500). In total the image shows data from 17 triggers after each other.
![Example](docs/example.png)
## Plugin info
```
Plugin info:
Index = 1
Name = ecmcPlugin_Scope
Description = Scope plugin for use with ecmc.
Option description =
DBG_PRINT=<1/0> : Enables/disables printouts from plugin, default = disabled.
SOURCE=<source> : Ec source variable (example: ec0.s1.mm.CH1_ARRAY).
RESULT_ELEMENTS=<Result buffer size> : Data points to collect, default = 4096.
SOURCE_NEXTTIME=<nexttime> : Ec next sync time for source (example: ec0.s1.NEXTTIME)
TRIGG=<trigger> : Ec trigg time (example: ec0.s2.LATCH_POS).
ENABLE=<1/0> : Enable data acq, defaults to enabled.
Filename = /home/dev/projects/e3-ecmcPlugin_Scope/ecmcPlugin_Scope-loc/O.7.0.4_linux-x86_64/libecmcPlugin_Scope.so
Config string = SOURCE=ec0.s35.mm.CH1_ARRAY;DBG_PRINT=0;TRIGG=ec0.s1.CH1_LATCH_POS;SOURCE_NEXTTIME=ec0.s35.NEXT_TIME;RESULT_ELEMENTS=500;
Version = 2
Interface version = 65536 (ecmc = 65536)
max plc funcs = 64
max plc func args = 10
max plc consts = 64
Construct func = @0x7f28a4eca2a0
Enter realtime func = @0x7f28a4eca300
Exit realtime func = @0x7f28a4eca290
Realtime func = @0x7f28a4eca2f0
Destruct func = @0x7f28a4eca2c0
dlhandle = @0x1883050
Plc functions:
funcs[00]:
Name = "scope_enable(arg0, arg1);"
Desc = scope_enable(index,enable) : Enable/disaable scope[index].
Arg count = 2
func = @0x7f28a4eca310
Plc constants:
```
# Troubleshooting
## Missed triggers
The plugin can not handle new triggers before the acquistion from the previous trigger is completed. Therefore the maximum triggering rate depends on the amount of data that should be acquired.
Therfore, in order to handle higher triggering rates, the result element count might need lowering (see above options).
Missed triggers can also be a result of bad syncronized dc clocks. If the dc-clocks are syncing properly the value of "NEXT_TIME" should always be after the trigger value (since NEXT_TIME should occur in the future).
The time between NEXT_TIME and trigger can be diagnosed by the "ScanToTriggSamples" pv like in this example (NELM=100):
```
camonitor IOC_TEST:Plugin-Scope0-ScanToTriggSamples
IOC_TEST:Plugin-Scope0-ScanToTriggSamples 2020-09-30 08:40:20.680888 91
IOC_TEST:Plugin-Scope0-ScanToTriggSamples 2020-09-30 08:40:20.703867 118
IOC_TEST:Plugin-Scope0-ScanToTriggSamples 2020-09-30 08:40:20.906862 115
IOC_TEST:Plugin-Scope0-ScanToTriggSamples 2020-09-30 08:40:20.944867 87
IOC_TEST:Plugin-Scope0-ScanToTriggSamples 2020-09-30 08:40:21.019865 102
IOC_TEST:Plugin-Scope0-ScanToTriggSamples 2020-09-30 08:40:21.282872 93
IOC_TEST:Plugin-Scope0-ScanToTriggSamples 2020-09-30 08:40:21.362859 99
IOC_TEST:Plugin-Scope0-ScanToTriggSamples 2020-09-30 08:40:21.470863 102
```
The value should always be 0 < value < 2*NELM (NELM = Oversamplefactor or samples per ethercat cycle) which means that the trigger occured up to 2*NELM ago.
If the value is outside these limts the trigger will be rejected. The reason could be badly syncrobized dc-clocks (see below).
## Slave time syncing
If the dc time syncronization of the slaves is not working properly then the timestamps from both trigger and analog i/o will drift apart resulting in lost triggers and currupted data.
This could be related to that an old version of the etherlab master is installed and needs to be upgraded.
NOTE: The dc clocks in the slaves start to syncronize to the reference clocks as soon as the ioc enter runtime. This procedure can take normally a few seconds but can also take minutes depending on how many slaves in the network. During this startup syncronization phase the dc clock of the trigger and the analog input slaves may be out of sync resulting in acquisition of wrong data.
### Verify dc diagnostics
#### ethercat master command
Check ethercat master dc diagnostics by issueing the "etehrcat master" command (when ecmc-ioc is running):
```
dev@mcag-epics4 ~ $ ethercat master
Master0
Phase: Operation
Active: yes
Slaves: 49
Ethernet devices:
Main: c0:3f:d5:66:24:13 (attached)
Link: UP
Tx frames: 1419695
Tx bytes: 553305060
Rx frames: 1419694
Rx bytes: 553304557
Tx errors: 0
Tx frame rate [1/s]: 1000 1000 1000
Tx rate [KByte/s]: 498.0 498.0 498.0
Rx frame rate [1/s]: 1000 1000 1000
Rx rate [KByte/s]: 498.0 498.0 498.0
Common:
Tx frames: 1419695
Tx bytes: 553305060
Rx frames: 1419694
Rx bytes: 553304557
Lost frames: 0
Tx frame rate [1/s]: 1000 1000 1000
Tx rate [KByte/s]: 498.0 498.0 498.0
Rx frame rate [1/s]: 1000 1000 1000
Rx rate [KByte/s]: 498.0 498.0 498.0
Loss rate [1/s]: 0 -0 0
Frame loss [%]: 0.0 -0.0 0.0
Distributed clocks:
Reference clock: Slave 0
DC reference time: 654699808777655501
Application time: 654700465153733449
2020-09-29 13:14:25.153733449
```
The last section of the printout lists some dc information of the master. Check teh following:
* The "Reference clock" should be assigned to a slave.
* The "DC reference time" should have a resonable value.
* The "Application time" should have a resonable value.
#### ethercat slaves command
Check the dc diagnostics for both the triggering and analog input slave bu the ethercat slaves command (when ecmc-ioc is running).
Example: Trigger slave id = 1
```
ev@mcag-epics4 ~ $ ethercat slaves -p1 -v
=== Master 0, Slave 1 ===
Device: Main
State: OP
Flag: +
Identity:
Vendor Id: 0x00000002
Product code: 0x04e43052
Revision number: 0x00150000
Serial number: 0x00000000
DL information:
FMMU bit operation: no
Distributed clocks: yes, 64 bit
DC system time transmission delay: 140 ns
Port Type Link Loop Signal NextSlave RxTime [ns] Diff [ns] NextDc [ns]
0 EBUS up open yes 0 1224884182 0 140
1 EBUS up open yes 2 1224891012 6830 154
2 N/A down closed no - - - -
3 N/C down closed no - - - -
General:
Group: DigIn
Image name: TERM_DI
Order number: EL1252
Device name: EL1252 2K. Fast Dig. Eingang 24V, 1<>s, DC Latch
Flags:
Enable SafeOp: no
Enable notLRW: no
Current consumption: 110 mA
```
Example: analog input slave id = 35
```
dev@mcag-epics4 ~ $ ethercat slaves -p35 -v
=== Master 0, Slave 35 ===
Device: Main
State: OP
Flag: +
Identity:
Vendor Id: 0x00000002
Product code: 0x0e763052
Revision number: 0x00030000
Serial number: 0x00000000
DL information:
FMMU bit operation: no
Distributed clocks: yes, 32 bit
DC system time transmission delay: 9088 ns
Port Type Link Loop Signal NextSlave RxTime [ns] Diff [ns] NextDc [ns]
0 EBUS up open yes 34 1765139745 0 170
1 EBUS down closed no - - - -
2 N/A down closed no - - - -
3 N/A down closed no - - - -
General:
Group: AnaInFast
Image name: TERM_AI
Order number: EL3702
Device name: EL3702 2K. Ana. Eingang +/-10V, DIFF, Oversample
Flags:
Enable SafeOp: no
Enable notLRW: no
Current consumption: 200 mA
```
Check the following for both slaves:
* "DC system time transmission delay" is a non zero value (like above)
* "Distributed clocks" is "yes" and 32 or 64 bit
If status of the above commands are not according to the examples then the etherlab master probbaly needs to be upgraded/reinstalled. Use the etherlab master repo: https://github.com/icshwi/etherlabmaster/ (or for ESS install via cs-entry.)

0
iocsh/.keep Normal file
View File

View File

@@ -0,0 +1,63 @@
##############################################################################
# Test to make a triggered scope
##############################################################################
## Initiation:
epicsEnvSet("IOC" ,"$(IOC="IOC_TEST")")
epicsEnvSet("ECMCCFG_INIT" ,"") #Only run startup once (auto at PSI, need call at ESS), variable set to "#" in startup.cmd
epicsEnvSet("SCRIPTEXEC" ,"$(SCRIPTEXEC="iocshLoad")")
require ecmccfg develop
# run module startup.cmd (only needed at ESS PSI auto call at require)
$(ECMCCFG_INIT)$(SCRIPTEXEC) ${ecmccfg_DIR}startup.cmd, "IOC=$(IOC),ECMC_VER=develop,stream_VER=2.8.10, EC_RATE=1000"
##############################################################################
## Config hardware:
## Sampel rate in milliseconds
epicsEnvSet("ECMC_SAMPLE_RATE_MS", "${RATE="1"}")
# Choose sample rate (2, 10 or 100)
epicsEnvSet("ECMC_OVER_SMP", "${OVERSAMP="100"}")
# EL1252: trigger on pos edge timestamp
epicsEnvSet("SLAVE_NUM_TRIGG", "6")
${SCRIPTEXEC} ${ecmccfg_DIR}addSlave.cmd, "SLAVE_ID=$(SLAVE_NUM_TRIGG), HW_DESC=EL1252"
# EL3702: Note: Set NELM to define oversampling rate
epicsEnvSet("SLAVE_NUM_AI", "11")
${SCRIPTEXEC} ${ecmccfg_DIR}addSlave.cmd, "SLAVE_ID=$(SLAVE_NUM_AI), HW_DESC=EL3702, NELM=${ECMC_OVER_SMP}"
#ecmcConfigOrDie "Cfg.EcSelectReferenceDC(0,$(SLAVE_NUM_TRIGG))"
#Apply hardware configuration
ecmcConfigOrDie "Cfg.EcApplyConfig(1)"
########################################################################s######
## Load plugin: Scope
require ecmc_plugin_scope master # to get access to db file..
epicsEnvSet(RESULT_NELM, 500)
epicsEnvSet(ECMC_PLUGIN_FILNAME,"${HOME}/epics/base-7.0.4/require/3.3.0/siteMods/ecmc_plugin_scope/master/lib/${EPICS_HOST_ARCH=linux-x86_64}/libecmcPlugin_Scope.so")
epicsEnvSet(ECMC_PLUGIN_CONFIG,"SOURCE=ec0.s${SLAVE_NUM_AI}.mm.CH1_ARRAY;DBG_PRINT=0;TRIGG=ec0.s${SLAVE_NUM_TRIGG}.CH1_LATCH_POS;SOURCE_NEXTTIME=ec0.s${SLAVE_NUM_AI}.NEXT_TIME;RESULT_ELEMENTS=${RESULT_NELM};")
${SCRIPTEXEC} ${ecmccfg_DIR}loadPlugin.cmd, "PLUGIN_ID=1,FILE=${ECMC_PLUGIN_FILNAME},CONFIG='${ECMC_PLUGIN_CONFIG}', REPORT=1"
epicsEnvUnset(ECMC_PLUGIN_FILNAME)
epicsEnvUnset(ECMC_PLUGIN_CONFIG)
dbLoadRecords("ecmcPluginScope.template","P=$(IOC):,PORT=${ECMC_ASYN_PORT},INDEX=0,RESULT_NELM=${RESULT_NELM},RESULT_DTYP=asynInt16ArrayIn,RESULT_FTVL=SHORT")
epicsEnvUnset(RESULT_NELM, 1024)
##############################################################################
############# Configure diagnostics:
ecmcConfigOrDie "Cfg.EcSetDiagnostics(1)"
ecmcConfigOrDie "Cfg.EcEnablePrintouts(0)"
ecmcConfigOrDie "Cfg.EcSetDomainFailedCyclesLimit(100)"
# go active
$(SCRIPTEXEC) ($(ecmccfg_DIR)setAppMode.cmd)
iocInit()
dbl > pvs.log

0
qt/.keep Normal file
View File

0
src/Db/.keep Normal file
View File

View File

@@ -0,0 +1,90 @@
# Data source
record(waveform,"$(P)Plugin-Scope${INDEX}-DataSource"){
field(DESC, "Data source name")
field(PINI, "1")
field(DTYP, "asynInt8ArrayIn")
field(INP, "@asyn(${PORT},$(ADDR=0),$(TIMEOUT=1000))T_SMP_MS=$(T_SMP_MS=1000)/TYPE=asynInt8ArrayIn/plugin.scope${INDEX}.source?")
field(FTVL, "CHAR")
field(NELM, "1024")
field(SCAN, "I/O Intr")
field(TSE, "0")
}
record(waveform,"$(P)Plugin-Scope${INDEX}-TriggSource"){
field(DESC, "Trigger source name")
field(PINI, "1")
field(DTYP, "asynInt8ArrayIn")
field(INP, "@asyn(${PORT},$(ADDR=0),$(TIMEOUT=1000))T_SMP_MS=$(T_SMP_MS=1000)/TYPE=asynInt8ArrayIn/plugin.scope${INDEX}.trigg?")
field(FTVL, "CHAR")
field(NELM, "1024")
field(SCAN, "I/O Intr")
field(TSE, "0")
}
record(waveform,"$(P)Plugin-Scope${INDEX}-NextTimeSource"){
field(DESC, "Trigger source name")
field(PINI, "1")
field(DTYP, "asynInt8ArrayIn")
field(INP, "@asyn(${PORT},$(ADDR=0),$(TIMEOUT=1000))T_SMP_MS=$(T_SMP_MS=1000)/TYPE=asynInt8ArrayIn/plugin.scope${INDEX}.nexttime?")
field(FTVL, "CHAR")
field(NELM, "1024")
field(SCAN, "I/O Intr")
field(TSE, "0")
}
# result
record(waveform,"$(P)Plugin-Scope${INDEX}-Data-Act"){
info(asyn:FIFO, "1000")
field(DESC, "Result data")
field(PINI, "1")
field(DTYP, "${RESULT_DTYP}")
field(INP, "@asyn(${PORT},$(ADDR=0),$(TIMEOUT=1000))T_SMP_MS=$(T_SMP_MS=-1)/TYPE=${RESULT_DTYP}/plugin.scope${INDEX}.resultdata?")
field(FTVL, "${RESULT_FTVL}")
field(NELM, "${RESULT_NELM}")
field(SCAN, "I/O Intr")
field(TSE, "0")
}
record(bo,"$(P)Plugin-Scope${INDEX}-Enable"){
field(DESC, "FFT Enable")
field(DTYP,"asynInt32")
field(OUT, "@asyn(${PORT},$(ADDR=0),$(TIMEOUT=1000))T_SMP_MS=$(T_SMP_MS=1000)/TYPE=asynInt32/plugin.scope${INDEX}.enable=")
field(ZNAM,"FALSE")
field(ONAM,"TRUE")
field(DOL, "0")
field(VAL, "0")
}
record(ai,"$(P)Plugin-Scope${INDEX}-MissTriggCntAct"){
field(PINI, "1")
field(DESC, "Missed trigger counter")
field(DTYP,"asynInt32")
field(INP, "@asyn(${PORT},$(ADDR=0),$(TIMEOUT=1000))T_SMP_MS=$(T_SMP_MS=1000)/TYPE=asynInt32/plugin.scope${INDEX}.missed?")
field(SCAN, "I/O Intr")
}
record(ai,"$(P)Plugin-Scope${INDEX}-ScanToTriggSamples"){
field(PINI, "1")
field(DESC, "Samples between now and trigger []")
field(DTYP,"asynFloat64")
field(INP, "@asyn(${PORT},$(ADDR=0),$(TIMEOUT=1000))T_SMP_MS=$(T_SMP_MS=1000)/TYPE=asynFloat64/plugin.scope${INDEX}.scantotrigg?")
field(SCAN, "I/O Intr")
}
record(ai,"$(P)Plugin-Scope${INDEX}-TriggCntAct"){
field(PINI, "1")
field(DESC, "Trigger counter")
field(DTYP,"asynInt32")
field(INP, "@asyn(${PORT},$(ADDR=0),$(TIMEOUT=1000))T_SMP_MS=$(T_SMP_MS=1000)/TYPE=asynInt32/plugin.scope${INDEX}.count?")
field(SCAN, "I/O Intr")
}
#record(bo,"$(P)Plugin-Scope${INDEX}-Trigg"){
# field(DESC, "FFT Trigg measurement")
# field(DTYP,"asynInt32")
# field(OUT, "@asyn(${PORT},$(ADDR=0),$(TIMEOUT=1000))T_SMP_MS=$(T_SMP_MS=1000)/TYPE=asynInt32/plugin.scope${INDEX}.trigg")
# field(ZNAM,"FALSE")
# field(ONAM,"TRUE")
# field(DOL, "0")
# field(VAL, "0")
#}

0
src/src/.keep Normal file
View File

158
src/src/ecmcPluginScope.c Normal file
View File

@@ -0,0 +1,158 @@
/*************************************************************************\
* Copyright (c) 2019 European Spallation Source ERIC
* ecmc is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
*
* ecmcPluginScope.cpp
*
* Created on: Sept 21, 2020
* Author: anderssandstrom
*
\*************************************************************************/
// Needed to get headers in ecmc right...
#define ECMC_IS_PLUGIN
#define ECMC_EXAMPLE_PLUGIN_VERSION 2
#ifdef __cplusplus
extern "C" {
#endif // ifdef __cplusplus
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "ecmcPluginDefs.h"
#include "ecmcScopeDefs.h"
#include "ecmcScopeWrap.h"
static int lastEcmcError = 0;
static char* lastConfStr = NULL;
/** Optional.
* Will be called once after successfull load into ecmc.
* Return value other than 0 will be considered error.
* configStr can be used for configuration parameters.
**/
int scopeConstruct(char *configStr)
{
//This module is allowed to load several times so no need to check if loaded
// create FFT object and register data callback
lastConfStr = strdup(configStr);
return createScope(configStr);
}
/** Optional function.
* Will be called once at unload.
**/
void scopeDestruct(void)
{
deleteAllScopes();
if(lastConfStr){
free(lastConfStr);
}
}
/** Optional function.
* Will be called each realtime cycle if definded
* ecmcError: Error code of ecmc. Makes it posible for
* this plugin to react on ecmc errors
* Return value other than 0 will be considered to be an error code in ecmc.
**/
int scopeRealtime(int ecmcError)
{
lastEcmcError = ecmcError;
return executeScopes();
}
/** Link to data source here since all sources should be availabe at this stage
* (for example ecmc PLC variables are defined only at enter of realtime)
**/
int scopeEnterRT(){
return linkDataToScopes();
}
/** Optional function.
* Will be called once just before leaving realtime mode
* Return value other than 0 will be considered error.
**/
int scopeExitRT(void){
return 0;
}
// // Plc function for clear of buffers
// double scope_clear(double index) {
// return (double)clearScope((int)index);
// }
// Plc function for enable
double scope_enable(double index, double enable) {
return (double)enableScope((int)index, (int)enable);
}
// // Plc function for trigg new measurement (will clear buffers)
// double scope_trigg(double index) {
// return (double)triggScope((int)index);
// }
// Register data for plugin so ecmc know what to use
struct ecmcPluginData pluginDataDef = {
// Allways use ECMC_PLUG_VERSION_MAGIC
.ifVersion = ECMC_PLUG_VERSION_MAGIC,
// Name
.name = "ecmcPlugin_Scope",
// Description
.desc = "Scope plugin for use with ecmc.",
// Option description
.optionDesc = "\n "ECMC_PLUGIN_DBG_PRINT_OPTION_CMD"<1/0> : Enables/disables printouts from plugin, default = disabled.\n"
" "ECMC_PLUGIN_SOURCE_OPTION_CMD"<source> : Ec source variable (example: ec0.s1.mm.CH1_ARRAY).\n"
" "ECMC_PLUGIN_RESULT_ELEMENTS_OPTION_CMD"<Result buffer size> : Data points to collect, default = 4096.\n"
" "ECMC_PLUGIN_SOURCE_NEXTTIME_OPTION_CMD"<nexttime> : Ec next sync time for source (example: ec0.s1.NEXTTIME)\n"
" "ECMC_PLUGIN_TRIGG_OPTION_CMD"<trigger> : Ec trigg time (example: ec0.s2.LATCH_POS).\n"
" "ECMC_PLUGIN_ENABLE_OPTION_CMD"<1/0> : Enable data acq, defaults to enabled.\n"
,
// Plugin version
.version = ECMC_EXAMPLE_PLUGIN_VERSION,
// Optional construct func, called once at load. NULL if not definded.
.constructFnc = scopeConstruct,
// Optional destruct func, called once at unload. NULL if not definded.
.destructFnc = scopeDestruct,
// Optional func that will be called each rt cycle. NULL if not definded.
.realtimeFnc = scopeRealtime,
// Optional func that will be called once just before enter realtime mode
.realtimeEnterFnc = scopeEnterRT,
// Optional func that will be called once just before exit realtime mode
.realtimeExitFnc = scopeExitRT,
// PLC funcs
.funcs[0] =
{ /*----fft_clear----*/
// Function name (this is the name you use in ecmc plc-code)
.funcName = "scope_enable",
// Function description
.funcDesc = "scope_enable(index,enable) : Enable/disaable scope[index].",
/**
* 12 different prototypes allowed (only doubles since reg in plc).
* Only funcArg${argCount} func shall be assigned the rest set to NULL.
**/
.funcArg0 = NULL,
.funcArg1 = NULL,
.funcArg2 = scope_enable,
.funcArg3 = NULL,
.funcArg4 = NULL,
.funcArg5 = NULL,
.funcArg6 = NULL,
.funcArg7 = NULL,
.funcArg8 = NULL,
.funcArg9 = NULL,
.funcArg10 = NULL,
.funcGenericObj = NULL,
},
.consts[0] = {0}, // last element set all to zero..
};
ecmc_plugin_register(pluginDataDef);
# ifdef __cplusplus
}
# endif // ifdef __cplusplus

1002
src/src/ecmcScope.cpp Normal file

File diff suppressed because it is too large Load Diff

131
src/src/ecmcScope.h Normal file
View File

@@ -0,0 +1,131 @@
/*************************************************************************\
* Copyright (c) 2019 European Spallation Source ERIC
* ecmc is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
*
* ecmcScope.h
*
* Created on: Mar 22, 2020
* Author: anderssandstrom
*
\*************************************************************************/
#ifndef ECMC_SCOPE_H_
#define ECMC_SCOPE_H_
#include <stdexcept>
#include "ecmcDataItem.h"
#include "ecmcAsynPortDriver.h"
#include "ecmcScopeDefs.h"
#include "inttypes.h"
#include <string>
typedef enum {
ECMC_SCOPE_STATE_INVALID, /**Invalid. */
ECMC_SCOPE_STATE_WAIT_TRIGG, /**Waiting for trigger. */
ECMC_SCOPE_STATE_WAIT_NEXT, /**Waiting analog. (trigger newer than next ai time)*/
ECMC_SCOPE_STATE_COLLECT, /**Filling buffer (waiting for data). */
} ecmcScopeState;
class ecmcScope {
public:
/** ecmc Scope class
* This object can throw:
* - bad_alloc
* - invalid_argument
* - runtime_error
* - out_of_range
*/
ecmcScope(int scopeIndex, // index of this object
char* configStr);
~ecmcScope();
// Add data to buffer (called from "external" callback)
// void dataUpdatedCallback(uint8_t* data,
// size_t size,
// ecmcEcDataType dt);
// Call just before realtime because then all data sources should be available
void connectToDataSources();
void setEnable(int enable);
//void clearBuffers();
void triggScope();
void execute();
private:
void parseConfigStr(char *configStr);
void addDataToBuffer(double data);
bool sourceDataTypeSupported(ecmcEcDataType dt);
void initAsyn();
int64_t timeDiff();
asynParamType getResultAsynDTFromEcDT(ecmcEcDataType ecDT);
void setWaitForNextTrigg();
uint8_t* resultDataBuffer_;
uint8_t* lastScanSourceDataBuffer_;
size_t resultDataBufferBytes_;
size_t bytesInResultBuffer_;
ecmcDataItem *sourceDataItem_;
ecmcDataItemInfo *sourceDataItemInfo_;
ecmcDataItem *sourceDataNexttimeItem_;
ecmcDataItemInfo *sourceDataNexttimeItemInfo_;
ecmcDataItem *sourceTriggItem_;
ecmcDataItemInfo *sourceTriggItemInfo_;
int dataSourceLinked_; // To avoid link several times
int objectId_; // Unique object id
int triggOnce_;
int firstTrigg_;
uint64_t triggTime_;
uint64_t oldTriggTime_;
uint64_t sourceNexttime_;
int64_t sourceSampleRateNS_; // nanoseconds
ecmcScopeState scopeState_;
uint64_t ecmcSmapleTimeNS_;
int64_t sourceElementsPerSample_;
size_t elementsInResultBuffer_;
double samplesSinceLastTrigg_;
// Config options
char* cfgDataSourceStr_; // Config: data source string
char* cfgDataNexttimeStr_; // Config: data source string
char* cfgTriggStr_; // Config: trigg string
int cfgDbgMode_; // Config: allow dbg printouts
size_t cfgBufferElementCount_; // Config: Data set size
int cfgEnable_; // Config: Enable data acq./calc.
int missedTriggs_;
int triggerCounter_;
// Asyn
ecmcAsynDataItem *sourceStrParam_;
ecmcAsynDataItem *triggStrParam_;
ecmcAsynDataItem *enbaleParam_;
ecmcAsynDataItem *resultParam_;
ecmcAsynDataItem *sourceNexttimeStrParam_;
ecmcAsynDataItem *asynMissedTriggs_;
ecmcAsynDataItem *asynTriggerCounter_;
ecmcAsynDataItem *asynTimeTrigg2Sample_;
// Some generic utility functions
static uint8_t getUint8(uint8_t* data);
static int8_t getInt8(uint8_t* data);
static uint16_t getUint16(uint8_t* data);
static int16_t getInt16(uint8_t* data);
static uint32_t getUint32(uint8_t* data);
static int32_t getInt32(uint8_t* data);
static uint64_t getUint64(uint8_t* data);
static int64_t getInt64(uint8_t* data);
static float getFloat32(uint8_t* data);
static double getFloat64(uint8_t* data);
static size_t getEcDataTypeByteSize(ecmcEcDataType dt);
static void printEcDataArray(uint8_t* data,
size_t size,
ecmcEcDataType dt,
int objId);
static std::string to_string(int value);
};
#endif /* ECMC_SCOPE_H_ */

28
src/src/ecmcScopeDefs.h Normal file
View File

@@ -0,0 +1,28 @@
/*************************************************************************\
* Copyright (c) 2019 European Spallation Source ERIC
* ecmc is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
*
* ecmcScopeDefs.h
*
* Created on: Sept 21, 2020
* Author: anderssandstrom
* Credits to https://github.com/sgreg/dynamic-loading
*
\*************************************************************************/
#ifndef ECMC_SCOPE_DEFS_H_
#define ECMC_SCOPE_DEFS_H_
// Options
#define ECMC_PLUGIN_DBG_PRINT_OPTION_CMD "DBG_PRINT="
#define ECMC_PLUGIN_SOURCE_OPTION_CMD "SOURCE="
#define ECMC_PLUGIN_SOURCE_NEXTTIME_OPTION_CMD "SOURCE_NEXTTIME="
#define ECMC_PLUGIN_TRIGG_OPTION_CMD "TRIGG="
#define ECMC_PLUGIN_RESULT_ELEMENTS_OPTION_CMD "RESULT_ELEMENTS="
#define ECMC_PLUGIN_ENABLE_OPTION_CMD "ENABLE="
// Default size (must be n²)
#define ECMC_PLUGIN_DEFAULT_BUFFER_SIZE 4096
#endif /* ECMC_SCOPE_DEFS_H_ */

121
src/src/ecmcScopeWrap.cpp Normal file
View File

@@ -0,0 +1,121 @@
/*************************************************************************\
* Copyright (c) 2019 European Spallation Source ERIC
* ecmc is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
*
* ecmcScopeWrap.cpp
*
* Created on: Sept 21, 2020
* Author: anderssandstrom
* Credits to https://github.com/sgreg/dynamic-loading
*
\*************************************************************************/
// Needed to get headers in ecmc right...
#define ECMC_IS_PLUGIN
#include <vector>
#include <stdexcept>
#include <string>
#include "ecmcScopeWrap.h"
#include "ecmcScope.h"
#include "ecmcScopeDefs.h"
#define ECMC_PLUGIN_PORTNAME_PREFIX "PLUGIN.SCOPE"
#define ECMC_PLUGIN_SCOPE_ERROR_CODE 1
static std::vector<ecmcScope*> scopes;
static int scopeObjCounter = 0;
int createScope(char* configStr) {
// create new ecmcFFT object
ecmcScope* scope = NULL;
try {
scope = new ecmcScope(scopeObjCounter, configStr);
}
catch(std::exception& e) {
if(scope) {
delete scope;
}
printf("Exception: %s. Plugin will unload.\n",e.what());
return ECMC_PLUGIN_SCOPE_ERROR_CODE;
}
scopes.push_back(scope);
scopeObjCounter++;
return 0;
}
void deleteAllScopes() {
for(std::vector<ecmcScope*>::iterator pscope = scopes.begin(); pscope != scopes.end(); ++pscope) {
if(*pscope) {
delete (*pscope);
}
}
}
int linkDataToScopes() {
for(std::vector<ecmcScope*>::iterator pscope = scopes.begin(); pscope != scopes.end(); ++pscope) {
if(*pscope) {
try {
(*pscope)->connectToDataSources();
}
catch(std::exception& e) {
printf("Exception: %s. Plugin will unload.\n",e.what());
return ECMC_PLUGIN_SCOPE_ERROR_CODE;
}
}
}
return 0;
}
int enableScope(int scopeIndex, int enable) {
try {
scopes.at(scopeIndex)->setEnable(enable);
}
catch(std::exception& e) {
printf("Exception: %s. Scope index out of range.\n",e.what());
return ECMC_PLUGIN_SCOPE_ERROR_CODE;
}
return 0;
}
// int clearScope(int scopeIndex) {
// try {
// scopes.at(scopeIndex)->clearBuffers();
// }
// catch(std::exception& e) {
// printf("Exception: %s. Scope index out of range.\n",e.what());
// return ECMC_PLUGIN_SCOPE_ERROR_CODE;
// }
// return 0;
// }
int triggScope(int scopeIndex) {
try {
scopes.at(scopeIndex)->triggScope();
}
catch(std::exception& e) {
printf("Exception: %s. Scope index out of range.\n",e.what());
return ECMC_PLUGIN_SCOPE_ERROR_CODE;
}
return 0;
}
int executeScopes() {
try {
for(std::vector<ecmcScope*>::iterator pscope = scopes.begin(); pscope != scopes.end(); ++pscope) {
if(*pscope) {
(*pscope)->execute();
}
}
}
catch(std::exception& e) {
printf("Exception: %s.\n",e.what());
return ECMC_PLUGIN_SCOPE_ERROR_CODE;
}
return 0;
}

54
src/src/ecmcScopeWrap.h Normal file
View File

@@ -0,0 +1,54 @@
/*************************************************************************\
* Copyright (c) 2019 European Spallation Source ERIC
* ecmc is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
*
* ecmcScopeWrap.h
*
* Created on: Sept 21, 2020
* Author: anderssandstrom
*
\*************************************************************************/
#ifndef ECMC_SCOPE_WRAP_H_
#define ECMC_SCOPE_WRAP_H_
#include "ecmcScopeDefs.h"
# ifdef __cplusplus
extern "C" {
# endif // ifdef __cplusplus
int createScope(char *configStr);
int enableScope(int scopeIndex, int enable);
//int clearScope(int scopeIndex);
int triggScope(int scopeIndex);
int executeScopes();
/** \brief Link data to _all_ scope objects
*
* This tells the Scope lib to connect to ecmc to find it's data source.\n
* This function should be called just before entering realtime since then all\n
* data sources in ecmc will be definded (plc sources are compiled just before runtime\n
* so are only fist accesible now).\n
* \return 0 if success or otherwise an error code.\n
*/
int linkDataToScopes();
/** \brief Deletes all created scope objects\n
*
* Should be called when destructs.\n
*/
void deleteAllScopes();
# ifdef __cplusplus
}
# endif // ifdef __cplusplus
#endif /* ECMC_SCOPE_WRAP_H_ */