Compare commits
113 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9c77ecd894 | |||
| 9992bc0787 | |||
| 0b6092446a | |||
| 3f89bf94c7 | |||
| 5e10e7c929 | |||
|
|
6d7f2ef37e | ||
|
|
154c6fb410 | ||
|
|
c187fbc008 | ||
|
|
b5b0af71c3 | ||
|
|
eb381156e3 | ||
|
|
ed83287447 | ||
|
|
ed103c6a9e | ||
|
|
134ea6f646 | ||
|
|
531be5dc92 | ||
|
|
034abc827b | ||
|
|
334400afce | ||
|
|
c18e6d756d | ||
|
|
b1a35993f5 | ||
|
|
8e0a5d774c | ||
|
|
bff88f1722 | ||
|
|
86e9ce7c6c | ||
|
|
16034c550a | ||
|
|
9916c3bb9e | ||
|
|
d2a58cb762 | ||
|
|
5ff3d6c7fd | ||
|
|
d7a02309bb | ||
|
|
fca81993e9 | ||
|
|
686f6fdf97 | ||
|
|
eda35ba589 | ||
|
|
d8b89f9247 | ||
|
|
6061a168ff | ||
|
|
ab2ff7193f | ||
|
|
15d0007186 | ||
|
|
2d7a0fadac | ||
|
|
bcb6599527 | ||
|
|
d1cc5a4d8d | ||
|
|
256f0ef749 | ||
|
|
14cc799609 | ||
|
|
a7e9cb5782 | ||
|
|
a63bbd1ab6 | ||
|
|
f6d37e9b1f | ||
|
|
af98edf6af | ||
|
|
3e95098790 | ||
|
|
b89533100f | ||
|
|
82250a4c90 | ||
|
|
d34f6fe413 | ||
|
|
79a77c1375 | ||
|
|
985ba27bd3 | ||
|
|
dc9041f4a2 | ||
|
|
9f2e406899 | ||
|
|
d82c81e4ac | ||
|
|
360bd8fe28 | ||
|
|
58d8467af8 | ||
|
|
36bd49184d | ||
|
|
0eae0d9136 | ||
|
|
3995f1db17 | ||
|
|
682cb02962 | ||
|
|
2d42a0d0b9 | ||
|
|
53bf9fb861 | ||
|
|
2d5c5067bf | ||
|
|
b77c4bfa24 | ||
|
|
19bf3f7a32 | ||
|
|
f7839fc6a8 | ||
|
|
99f945a04b | ||
|
|
fd1b4bb9ef | ||
|
|
d289e89ab3 | ||
|
|
a2ff30f305 | ||
|
|
f3b8a2a059 | ||
|
|
a2a950f4de | ||
|
|
ad0b4f873e | ||
|
|
46679dfc3e | ||
|
|
5b518a0661 | ||
|
|
76f01227c0 | ||
|
|
7f2ef1d04d | ||
|
|
1a4c4a881f | ||
|
|
54949701d1 | ||
|
|
10e2935483 | ||
|
|
700d63de97 | ||
|
|
90f33f1200 | ||
|
|
d989b4c737 | ||
|
|
c462c38e77 | ||
|
|
9a21232296 | ||
|
|
5409f8e7f7 | ||
|
|
d5bf120339 | ||
|
|
111f6f460a | ||
|
|
7fdd51b0cd | ||
|
|
e5f696a1eb | ||
|
|
1b88210a60 | ||
|
|
5e6ed01cd7 | ||
|
|
188f05b8a1 | ||
|
|
74b527f306 | ||
|
|
cb9fe2a361 | ||
|
|
6e84d4bf0c | ||
|
|
8ab585652e | ||
|
|
4e0ac9462a | ||
|
|
fa62fbf918 | ||
|
|
0b7954e478 | ||
|
|
4361b083c5 | ||
|
|
020d7932af | ||
|
|
d90918ac9e | ||
|
|
8f5ea3acd9 | ||
|
|
e5e754f5e8 | ||
|
|
c42c284b00 | ||
|
|
78d6cd5a12 | ||
|
|
afb93a2b00 | ||
|
|
399023e023 | ||
|
|
02d9ef3244 | ||
|
|
e54cca6261 | ||
|
|
4d4dd047c2 | ||
|
|
2ece5fd754 | ||
|
|
00fb8e4c81 | ||
|
|
1bd8d56f1f | ||
|
|
a6456894ae |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -14,4 +14,5 @@ core.*
|
||||
*PVs.list
|
||||
*-loc/*.Makefile
|
||||
ecmc_plugin_fft/*.Makefile
|
||||
*__*
|
||||
*__*
|
||||
O.*
|
||||
9
.gitmodules
vendored
9
.gitmodules
vendored
@@ -1,9 +0,0 @@
|
||||
[submodule "ecmc_plugin_socketcan/ecmc_plugin_socketcanApp/src/CANopenNode"]
|
||||
path = ecmc_plugin_socketcan/ecmc_plugin_socketcanApp/src/CANopenNode
|
||||
url = https://github.com/anderssandstrom/CANopenNode.git
|
||||
[submodule "ecmc_plugin_socketcan/ecmc_plugin_socketcanApp/src/CANopenSocket"]
|
||||
path = ecmc_plugin_socketcan/ecmc_plugin_socketcanApp/src/CANopenSocket
|
||||
url = https://github.com/anderssandstrom/CANopenSocket
|
||||
[submodule "ecmc_plugin_socketcan/ecmc_plugin_socketcanApp/src/openCANopen"]
|
||||
path = ecmc_plugin_socketcan/ecmc_plugin_socketcanApp/src/openCANopen
|
||||
url = https://github.com/marel-keytech/openCANopen
|
||||
45
GNUmakefile
Normal file
45
GNUmakefile
Normal file
@@ -0,0 +1,45 @@
|
||||
include /ioc/tools/driver.makefile
|
||||
|
||||
MODULE = ecmc_plugin_socketcan
|
||||
|
||||
# "Transfer" module name to plugin
|
||||
USR_CFLAGS +=-DECMC_PLUGIN_MODULE_NAME=${MODULE}
|
||||
|
||||
BUILDCLASSES = Linux
|
||||
ARCH_FILTER = deb10% deb12%
|
||||
|
||||
# Run 7.0.6 for now
|
||||
EXCLUDE_VERSIONS+=3 7.0.5 7.0.6 7.0.7
|
||||
|
||||
IGNORE_MODULES += asynMotor
|
||||
IGNORE_MODULES += motorBase
|
||||
|
||||
USR_CXXFLAGS += -std=c++17
|
||||
OPT_CXXFLAGS_YES = -O3
|
||||
|
||||
# dependencies
|
||||
ecmc_VERSION = 10.0
|
||||
|
||||
APPDB:=db
|
||||
APPSRC:=src
|
||||
|
||||
USR_CFLAGS += -shared -fPIC -Wall -Wextra
|
||||
USR_CPPFLAGS += -std=c++11
|
||||
USR_LDFLAGS += -lstdc++
|
||||
USR_INCLUDES += -I$(where_am_I)$(APPSRC)
|
||||
|
||||
TEMPLATES += $(wildcard $(APPDB)/*.db)
|
||||
TEMPLATES += $(wildcard $(APPDB)/*.template)
|
||||
TEMPLATES += $(wildcard $(APPDB)/*.substitutions)
|
||||
SOURCES += $(APPSRC)/ecmcPluginSocketCAN.c
|
||||
SOURCES += $(APPSRC)/ecmcSocketCAN.cpp
|
||||
SOURCES += $(APPSRC)/ecmcSocketCANWrap.cpp
|
||||
SOURCES += $(APPSRC)/ecmcSocketCANWriteBuffer.cpp
|
||||
SOURCES += $(APPSRC)/ecmcCANOpenSDO.cpp
|
||||
SOURCES += $(APPSRC)/ecmcCANOpenPDO.cpp
|
||||
SOURCES += $(APPSRC)/ecmcCANOpenDevice.cpp
|
||||
SOURCES += $(APPSRC)/ecmcCANOpenMaster.cpp
|
||||
SOURCES += $(APPSRC)/ecmcByteToArrayAsub.cpp
|
||||
|
||||
DBDS += $(APPSRC)/ecmcSocketCAN.dbd
|
||||
|
||||
486
README.md
486
README.md
@@ -1,3 +1,489 @@
|
||||
e3-ecmc_plugin_socketcan
|
||||
======
|
||||
ESS Site-specific EPICS module : ecmcPlugin_socketcan
|
||||
|
||||
# SocketCAN
|
||||
|
||||
This module adds some CAN support to ecmc based on SocketCAN:
|
||||
https://en.wikipedia.org/wiki/SocketCAN
|
||||
|
||||
## Hardware
|
||||
|
||||
This module have been tested with Kvaser Leaf Light v2 usb can interface.
|
||||
More info about how to setup this hardware can be found here:
|
||||
|
||||
[Kvaser Leaf V2 setup](kvaser/readme.md)
|
||||
|
||||
# Install ecmc plugin module
|
||||
|
||||
## Dependencies
|
||||
This module have dependencies to:
|
||||
|
||||
1. ecmc
|
||||
|
||||
3. asyn
|
||||
|
||||
## Configuration
|
||||
Ensure that these two files are correct:
|
||||
|
||||
1. configure/CONFIG_MODULE:
|
||||
```
|
||||
EPICS_MODULE_TAG:=master
|
||||
E3_MODULE_VERSION:=master
|
||||
ECMC_DEP_VERSION:=6.3.2
|
||||
ASYN_DEP_VERSION:=4.41.0
|
||||
```
|
||||
|
||||
2. configure/RELEASE:
|
||||
```
|
||||
EPICS_BASE:=${HOME}/epics/base-7.0.4
|
||||
E3_REQUIRE_VERSION:=3.4.0
|
||||
```
|
||||
|
||||
## Install
|
||||
|
||||
The module is installed like any other e3 module by "make install":
|
||||
```
|
||||
make install
|
||||
```
|
||||
Now the module should be ready for use.
|
||||
|
||||
## Load plugin into ecmc
|
||||
Like all ecmc plugins the plugin must be loaded into ecmc with the "loadPlugin.cmd" command.
|
||||
The loading is needed so that ecmc can link to the callbacks, plc functions and plc constants in the plugin.
|
||||
|
||||
The "loadPlugin.cmd" command basically takes three arguments:
|
||||
|
||||
1. PLUGIN_ID: This is just an index of the different plugins loaded into ecmc. So the first loaded plugin should have index 0.
|
||||
|
||||
2. FILE: Path to the "libecmc_plugin_socketcan.so" file.
|
||||
|
||||
3. CONFIG: Plugin configuration string. See below for details
|
||||
|
||||
### Configurations string "CONFIG"
|
||||
|
||||
The configuration string contains startup configs for the plugin. Currentlly the below configurations can be specified in the CONFIG string:
|
||||
```
|
||||
DBG_PRINT=<1/0> : Enables/disables printouts from plugin, default = disabled (=0).
|
||||
IF=<if name> : Sets can interface (example: can0, vcan0..).
|
||||
CONNECT=<1/0> : Auto connect to if at startup, default = autoconnect (=1).
|
||||
```
|
||||
Also see "Plugin" chapter below for more information about the plugin.
|
||||
|
||||
### Example
|
||||
Example of loading the plugin connecting to the "can0" interface:
|
||||
```
|
||||
## Load plugin:
|
||||
epicsEnvSet(ECMC_PLUGIN_FILNAME,"${HOME}/epics/base-7.0.4/require/${E3_REQUIRE_VERSION}/siteMods/ecmc_plugin_socketcan/master/lib/${EPICS_HOST_ARCH=linux-x86_64}/libecmc_plugin_socketcan.so")
|
||||
epicsEnvSet(ECMC_PLUGIN_CONFIG,"IF=can0;DBG_PRINT=0;")
|
||||
${SCRIPTEXEC} ${ecmccfg_DIR}loadPlugin.cmd, "PLUGIN_ID=0,FILE=${ECMC_PLUGIN_FILNAME},CONFIG='${ECMC_PLUGIN_CONFIG}', REPORT=1"
|
||||
epicsEnvUnset(ECMC_PLUGIN_FILNAME)
|
||||
epicsEnvUnset(ECMC_PLUGIN_CONFIG)
|
||||
```
|
||||
|
||||
A complete example can be found in the pmu905 repo where a R&S amplifier is interfaced using this module:
|
||||
https://github.com/anderssandstrom/ecmc_pmu905_can
|
||||
|
||||
|
||||
### Access to templates
|
||||
|
||||
In order to access the templates in the plugin a "require" is needed in the startup script:
|
||||
|
||||
```
|
||||
require ecmc_plugin_socketcan master
|
||||
|
||||
```
|
||||
|
||||
# CANOpen
|
||||
|
||||
This module implements a few of the CANOpen functionalities (but it is far from a full CANOpen Implementation).
|
||||
|
||||
The current implemented features are:
|
||||
|
||||
* Simple master:
|
||||
|
||||
* LSS (basic)
|
||||
|
||||
* NMT (Heartbeat, dev state)
|
||||
|
||||
* Sync
|
||||
|
||||
* Simple generic (slave) device
|
||||
|
||||
* SDO segmented r/w
|
||||
|
||||
* PDO r/w
|
||||
|
||||
These functionalities are configured through iocsh cmds:
|
||||
|
||||
* ecmcCANOpenAddMaster
|
||||
|
||||
* ecmcCANOpenAddDevice
|
||||
|
||||
* ecmcCANOpenAddSDO
|
||||
|
||||
* ecmcCANOpenAddPDO
|
||||
|
||||
## ecmcCANOpenAddMaster
|
||||
|
||||
Issueing "ecmcCANOpenAddMaster -h" in the iocsh you will get the following help printout:
|
||||
```
|
||||
ecmcCANOpenAddMaster -h
|
||||
Use ecmcCANOpenAddMaster(<name>, <node id>,....)
|
||||
<name> : Name of master device.
|
||||
<node id> : CANOpen node id of master.
|
||||
<LSS sample time ms> : Sample time for LSS.
|
||||
<Sync sample time ms> : Sample time for SYNC.
|
||||
<Heartbeat sample time ms> : Sample time for Heartbeat.
|
||||
```
|
||||
|
||||
Example to add a master with LSS, sync, heartbeat sample time of 1s:
|
||||
```
|
||||
ecmcCANOpenAddMaster("ecmcCANOpenMaster",0,1000,1000,1000)
|
||||
|
||||
# Load status records for device
|
||||
dbLoadRecords(ecmcPluginSocketCAN_Dev.template, "P=${ECMC_PREFIX},PORT=${ECMC_ASYN_PORT},ADDR=0,TIMEOUT=1,CH_ID=00,DEV_ID=0")
|
||||
```
|
||||
|
||||
Note:: You can only have one master in each ioc (one call to "ecmcCANOpenAddMaster").
|
||||
|
||||
## ecmcCANOpenAddDevice
|
||||
|
||||
Issueing "ecmcCANOpenAddDevice -h" in the iocsh you will get the following help printout:
|
||||
```
|
||||
ecmcCANOpenAddDevice -h
|
||||
Use ecmcCANOpenAddDevice(<name>, <node id>)
|
||||
<name> : Name of device.
|
||||
<node id> : CANOpen node id of device.
|
||||
<NMT Heartbeat timeout ms> : Timeout for NMT Heartbeat.\n")
|
||||
|
||||
```
|
||||
Example to add a device with node id 3 and a NMT heartbeat timeout of 3s:
|
||||
```
|
||||
ecmcCANOpenAddDevice("testDevice",3,3000)
|
||||
|
||||
# Load status records for device
|
||||
dbLoadRecords(ecmcPluginSocketCAN_Dev.template, "P=${ECMC_PREFIX},PORT=${ECMC_ASYN_PORT},ADDR=0,TIMEOUT=1,CH_ID=03,DEV_ID=3")
|
||||
```
|
||||
|
||||
Note:: You can only use the below commands (ecmcCANOpenAddSDO and ecmcCANOpenAddODO) to nodes of an existing device or master.
|
||||
|
||||
## ecmcCANOpenAddSDO
|
||||
|
||||
Issueing "ecmcCANOpenAddSDO -h" in the iocsh you will get the following help printout:
|
||||
```
|
||||
ecmcCANOpenAddSDO -h
|
||||
ecmcCANOpenAddSDO( in the iocsh<name>, <node id>,.....)
|
||||
<name> : Name of master device.
|
||||
<node id> : CANOpen node id of device/master.
|
||||
<cob id tx> : CANOpen cob id of Tx of slave SDO.
|
||||
<cob id rx> : CANOpen cob id of Rx of slave SDO.
|
||||
<dir> : Direction 1=write and 2=read.
|
||||
<ODIndex> : OD index of SDO.
|
||||
<ODSubIndex> : OD sub index of SDO.
|
||||
<ODSize> : OS Siz
|
||||
<readSampleTimeMs>: Sample time for read in ms (write is always on demand).
|
||||
```
|
||||
|
||||
A few examples:
|
||||
```
|
||||
# Example read SDO from device, will be cyclically read at an intervall of 7000ms:
|
||||
ecmcCANOpenAddSDO("analogValues",3,0x583,0x603,2,0x2640,0x0,56,7000)
|
||||
|
||||
# Load record for SDO data
|
||||
dbLoadRecords(ecmcPluginSocketCAN_SDO_input.template, "P=${ECMC_PREFIX},PORT=${ECMC_ASYN_PORT},ADDR=0,TIMEOUT=1,T_SMP_MS=${ECMC_SAMPLE_RATE_MS},TSE=${ECMC_TSE},NELM=${NELM=1},CH_ID=03,DEV_ID=3,suffix=AI03-Array,source=basicConfig,DTYP=asynInt8ArrayIn,FTVL=CHAR,NELM=7")
|
||||
|
||||
# Load record for SDO error
|
||||
dbLoadRecords(ecmcPluginSocketCAN_SDO_error.template, "P=${ECMC_PREFIX},PORT=${ECMC_ASYN_PORT},ADDR=0,TIMEOUT=1,T_SMP_MS=${ECMC_SAMPLE_RATE_MS},TSE=${ECMC_TSE},CH_ID=03,DEV_ID=3,suffix=SDO01-AnalogValuesErr,source=analogValues")
|
||||
|
||||
# Example write SDO to device, will be written on demand (when write data from epics):
|
||||
ecmcCANOpenAddSDO("basicConfig",3,0x583,0x603,1,0x2690,0x1,7,0)
|
||||
|
||||
# Load record for SDO data
|
||||
dbLoadRecords(ecmcPluginSocketCAN_SDO_output.template, "P=${ECMC_PREFIX},PORT=${ECMC_ASYN_PORT},ADDR=0,TIMEOUT=1,T_SMP_MS=${ECMC_SAMPLE_RATE_MS},TSE=${ECMC_TSE},NELM=${NELM=1},CH_ID=03,DEV_ID=3,suffix=BasicConfig,source=basicConfig,DTYP=asynInt8ArrayOut,FTVL=CHAR,NELM=7")
|
||||
|
||||
# Load record for SDO error
|
||||
dbLoadRecords(ecmcPluginSocketCAN_SDO_error.template, "P=${ECMC_PREFIX},PORT=${ECMC_ASYN_PORT},ADDR=0,TIMEOUT=1,T_SMP_MS=${ECMC_SAMPLE_RATE_MS},TSE=${ECMC_TSE},CH_ID=03,DEV_ID=3,suffix=SDO02-BasicConfigErr,source=basicConfig")
|
||||
|
||||
```
|
||||
## ecmcCANOpenAddPDO
|
||||
|
||||
Issueing "ecmcCANOpenAddPDO -h" in the iocsh you will get the following help printout:
|
||||
```
|
||||
ecmcCANOpenAddPDO -h
|
||||
Use "ecmcCANOpenAddPDO(<name>, <node id>
|
||||
<name> : Name of master device.
|
||||
<node id> : CANOpen node id of device/master.
|
||||
<cob id> : CANOpen cob id of PDO.
|
||||
<dir> : Direction 1=write and 2=read.
|
||||
<ODSize> : Size of PDO (max 8 bytes).
|
||||
<readTimeoutMs> : Readtimeout in ms.
|
||||
<writeCycleMs> : Cycle time for write (if <= 0 then only write on change).
|
||||
|
||||
```
|
||||
An example:
|
||||
|
||||
```
|
||||
# Read PDO, if not recived pdo within 10s the PDO object will go into error state:
|
||||
ecmcCANOpenAddPDO("status",3,0x183,2,8,10000,0)
|
||||
|
||||
# Load record for PDO data
|
||||
dbLoadRecords(ecmcPluginSocketCAN_PDO_input.template, "P=${ECMC_PREFIX},PORT=${ECMC_ASYN_PORT},ADDR=0,TIMEOUT=1,T_SMP_MS=${ECMC_SAMPLE_RATE_MS},TSE=${ECMC_TSE},NELM=${NELM=1},CH_ID=03,DEV_ID=3,suffix=PDO01-Array,source=status,NELM=8")
|
||||
|
||||
# Load record for PDO error
|
||||
dbLoadRecords(ecmcPluginSocketCAN_PDO_error.template, "P=${ECMC_PREFIX},PORT=${ECMC_ASYN_PORT},ADDR=0,TIMEOUT=1,T_SMP_MS=${ECMC_SAMPLE_RATE_MS},TSE=${ECMC_TSE},CH_ID=03,DEV_ID=3,suffix=PDO01-StatusErr,source=status")
|
||||
|
||||
```
|
||||
|
||||
# PVs
|
||||
|
||||
## Communication
|
||||
|
||||
The status of the CAN communication can be supervised by loading the "ecmcPluginSocketCAN_Com.template":
|
||||
|
||||
```
|
||||
dbLoadRecords(ecmcPluginSocketCAN_Com.template, "P=${ECMC_PREFIX},PORT=${ECMC_ASYN_PORT},ADDR=0,TIMEOUT=1")
|
||||
|
||||
# Will load:
|
||||
IOC_TEST:CAN-Stat-ComErr
|
||||
IOC_TEST:CAN-Stat-Connected
|
||||
```
|
||||
The Connected Pv show if the plugin is connected to socketcan and the hardware.
|
||||
The ComErr Pv shows error codes for reads and writes to socketcan socket.
|
||||
|
||||
## Device (also Master)
|
||||
|
||||
### ecmcPluginSocketCAN_Dev.template
|
||||
|
||||
The ecmcPluginSocketCAN_Dev.template loads a NMT PV:
|
||||
```
|
||||
IOC_TEST:CAN03-Stat-NMT
|
||||
```
|
||||
The PV tells wich state the device is in and can have the following values:
|
||||
* NMT_NOT_VALID : State not valid. Something is wrong.
|
||||
|
||||
* NMT_BOOT_UP : Device is booting
|
||||
|
||||
* NMT_STOPPED : Device is stopped
|
||||
|
||||
* NMT_OP : Device is operational (this is the normal state when running the ioc).
|
||||
|
||||
* NMT_PREOP : Device is in pre operational state
|
||||
|
||||
|
||||
## PDO
|
||||
|
||||
## ecmcPluginSocketCAN_PDO_input.template
|
||||
|
||||
The ecmcPluginSocketCAN_PDO_intput.template in used for input PDOS (data read from a device device to EPICS).
|
||||
The template only contains one PV that holds the data:
|
||||
|
||||
Example:
|
||||
```
|
||||
dbLoadRecords(ecmcPluginSocketCAN_PDO_input.template, "P=${ECMC_PREFIX},PORT=${ECMC_ASYN_PORT},ADDR=0,TIMEOUT=1,T_SMP_MS=${ECMC_SAMPLE_RATE_MS},TSE=${ECMC_TSE},CH_ID=03,DEV_ID=3,suffix=PDO01-Status_,source=status,NELM=8")
|
||||
|
||||
# Loads this PV:
|
||||
IOC_TEST:CAN03-PDO01-Status_
|
||||
```
|
||||
The data PV is an waveform of type uchar and length NELM.
|
||||
|
||||
## ecmcPluginSocketCAN_PDO_output.template
|
||||
|
||||
The ecmcPluginSocketCAN_PDO_output.template in used for output PDOS (data written from EPICS to device).
|
||||
The template only contains one PV that holds the data:
|
||||
|
||||
Example:
|
||||
```
|
||||
dbLoadRecords(ecmcPluginSocketCAN_PDO_output.template, "P=${ECMC_PREFIX},PORT=${ECMC_ASYN_PORT},ADDR=0,TIMEOUT=1,T_SMP_MS=${ECMC_SAMPLE_RATE_MS},TSE=${ECMC_TSE},CH_ID=00,DEV_ID=0,suffix=PDO01-Reset_, source=reset,NELM=8")
|
||||
|
||||
# Loads this PV:
|
||||
IOC_TEST:CAN03-PDO01-Reset_
|
||||
```
|
||||
The data PV is an waveform of type uchar and length NELM.
|
||||
|
||||
|
||||
## ecmcPluginSocketCAN_PDO_error.template
|
||||
|
||||
The ecmcPluginSocketCAN_PDO_error.template in used to read the PDO error code.
|
||||
|
||||
Example:
|
||||
```
|
||||
dbLoadRecords(ecmcPluginSocketCAN_PDO_error.template, "P=${ECMC_PREFIX},PORT=${ECMC_ASYN_PORT},ADDR=0,TIMEOUT=1,T_SMP_MS=${ECMC_SAMPLE_RATE_MS},TSE=${ECMC_TSE},CH_ID=00,DEV_ID=0,suffix=PDO01-ResetErr,source=reset")
|
||||
|
||||
# Loads this PV:
|
||||
IOC_TEST:CAN03-PDO01-ResetErr
|
||||
```
|
||||
|
||||
An error could for example be that the PDO data have not been recived within the correct time frame (readTimeoutMs).
|
||||
The PDO is in error state if the error code is not 0.
|
||||
|
||||
## SDO
|
||||
|
||||
## ecmcPluginSocketCAN_SDO_input.template
|
||||
|
||||
The ecmcPluginSocketCAN_SDO_intput.template in used for input SDOS (data read from a device device to EPICS).
|
||||
The template only contains one PV that holds the data:
|
||||
|
||||
Example:
|
||||
```
|
||||
dbLoadRecords(ecmcPluginSocketCAN_SDO_input.template, "P=${ECMC_PREFIX},PORT=${ECMC_ASYN_PORT},ADDR=0,TIMEOUT=1,T_SMP_MS=${ECMC_SAMPLE_RATE_MS},TSE=${ECMC_TSE},CH_ID=03,DEV_ID=3,suffix=SDO01-AnalogValues_, source=analogValues,DTYP=asynInt16ArrayIn,FTVL=SHORT,NELM=28")
|
||||
|
||||
# Loads this PV:
|
||||
IOC_TEST:CAN03-SDO01-AnalogValues_
|
||||
```
|
||||
The data PV is an waveform of type uchar and length NELM.
|
||||
|
||||
## ecmcPluginSocketCAN_SDO_output.template
|
||||
|
||||
The ecmcPluginSocketCAN_SDO_output.template in used for output SDOS (data written from EPICS to device).
|
||||
The template only contains one PV that holds the data:
|
||||
|
||||
Example:
|
||||
```
|
||||
dbLoadRecords(ecmcPluginSocketCAN_SDO_output.template,"P=${ECMC_PREFIX},PORT=${ECMC_ASYN_PORT},ADDR=0,TIMEOUT=1,T_SMP_MS=${ECMC_SAMPLE_RATE_MS},TSE=${ECMC_TSE},CH_ID=03,DEV_ID=3,suffix=SDO02-BasicConfig_, source=basicConfig,DTYP=asynInt8ArrayOut,FTVL=UCHAR,NELM=7")
|
||||
|
||||
# Loads this PV:
|
||||
IOC_TEST:CAN03-SDO02-BasicConfig_
|
||||
```
|
||||
The data PV is an waveform of type uchar and length NELM.
|
||||
|
||||
## ecmcPluginSocketCAN_SDO_error.template
|
||||
|
||||
The ecmcPluginSocketCAN_PSO_error.template in used to read the SDO error code.
|
||||
|
||||
Example:
|
||||
```
|
||||
dbLoadRecords(ecmcPluginSocketCAN_SDO_error.template, "P=${ECMC_PREFIX},PORT=${ECMC_ASYN_PORT},ADDR=0,TIMEOUT=1,T_SMP_MS=${ECMC_SAMPLE_RATE_MS},TSE=${ECMC_TSE},CH_ID=03,DEV_ID=3,suffix=SDO02-BasicConfigErr,source=basicConfig")
|
||||
|
||||
# Loads this PV:
|
||||
IOC_TEST:CAN03-SDO02-BasicConfigErr
|
||||
```
|
||||
|
||||
An error could for example be that the SDO data have not been recived within the correct time frame or slave is not reposning like it should.
|
||||
The SDO is in error state if the error code is not 0.
|
||||
|
||||
# Plugin
|
||||
|
||||
Plugin report printout:
|
||||
```
|
||||
Plugin info:
|
||||
Index = 0
|
||||
Name = ecmcPlugin_socketcan
|
||||
Description = SocketCAN plugin for use with ecmc.
|
||||
Option description =
|
||||
DBG_PRINT=<1/0> : Enables/disables printouts from plugin, default = disabled (=0).
|
||||
IF=<if name> : Sets can interface (example: can0, vcan0..).
|
||||
CONNECT=<1/0> : Auto connect to if at startup, default = autoconnect (=1).
|
||||
|
||||
Filename = /home/dev/epics/base-7.0.4/require/3.4.0/siteMods/ecmc_plugin_socketcan/master/lib/linux-x86_64/libecmc_plugin_socketcan.so
|
||||
Config string = IF=vcan0;DBG_PRINT=1;
|
||||
Version = 2
|
||||
Interface version = 65536 (ecmc = 65536)
|
||||
max plc funcs = 64
|
||||
max plc func args = 10
|
||||
max plc consts = 64
|
||||
Construct func = @0x7fb36b81d310
|
||||
Enter realtime func = @0x7fb36b81d220
|
||||
Exit realtime func = @0x7fb36b81d230
|
||||
Realtime func = @0x7fb36b81d270
|
||||
Destruct func = @0x7fb36b81d240
|
||||
dlhandle = @0xa45a50
|
||||
Plc functions:
|
||||
funcs[00]:
|
||||
Name = "can_connect();"
|
||||
Desc = double can_connect() : Connect to can interface (from config str).
|
||||
Arg count = 0
|
||||
func = @0x7fb36b81d280
|
||||
funcs[01]:
|
||||
Name = "can_connected();"
|
||||
Desc = double can_connected() : Connected to can interface.
|
||||
Arg count = 0
|
||||
func = @0x7fb36b81d2a0
|
||||
funcs[02]:
|
||||
Name = "can_add_write(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9);"
|
||||
Desc = double can_add_write(canId,len,data0..data7) : Add write frame to can interface output buffer.
|
||||
Arg count = 10
|
||||
func = @0x7fb36b81d2e0
|
||||
funcs[03]:
|
||||
Name = "can_last_writes_error();"
|
||||
Desc = double can_last_writes_error() : get error from last writes.
|
||||
Arg count = 0
|
||||
func = @0x7fb36b81d2c0
|
||||
Plc constants:
|
||||
```
|
||||
# ecmcByteToArray Asub record
|
||||
This module implements an asub record that can merge bytes into an array (waveform)
|
||||
|
||||
Usage:
|
||||
1. Link bytes to inputs A..S.
|
||||
|
||||
2. Link array to VALA.
|
||||
|
||||
3. Set size of output in NOVA. This also defines how many inputs will be used.
|
||||
|
||||
Note: Max 18 bytes (input A..S) will be merged into the array.
|
||||
|
||||
|
||||
EPICS database example of 7 bytes merged intio an array:
|
||||
```
|
||||
record(aSub, "$(P)CAN${CH_ID}-BasicConfigPackArray_") {
|
||||
field(INAM, "ecmcByteToArrayInit")
|
||||
field(SNAM, "ecmcByteToArray")
|
||||
field(FTA, "UCHAR")
|
||||
field(NOA, "1")
|
||||
field(INPA, "$(P)CAN${CH_ID}-BasicConfigB0_.VAL") # Byte 0
|
||||
field(FTB, "UCHAR")
|
||||
field(NOB, "1")
|
||||
field(INPB, "$(P)CAN${CH_ID}-VrefPwrCmdCalcB1_.VAL") # Byte 1
|
||||
field(FTC, "UCHAR")
|
||||
field(NOC, "1")
|
||||
field(INPC, "$(P)CAN${CH_ID}-VrefPwrCmdCalcB2_.VAL") # Byte 2
|
||||
field(FTD, "UCHAR")
|
||||
field(NOD, "1")
|
||||
field(INPD, "$(P)CAN${CH_ID}-VdcCtrlCmdCalcB3_.VAL") # Byte 3
|
||||
field(FTE, "UCHAR")
|
||||
field(NOE, "1")
|
||||
field(INPE, "$(P)CAN${CH_ID}-VdcCtrlCmdCalcB4_.VAL") # Byte 4
|
||||
field(FTF, "UCHAR")
|
||||
field(NOF, "1")
|
||||
field(INPF, "0") # Byte 5
|
||||
field(FTG, "UCHAR")
|
||||
field(NOG, "1")
|
||||
field(INPG, "0") # Byte 6
|
||||
field(FTVA, "UCHAR")
|
||||
field(OUTA, "$(P)CAN${CH_ID}-SDO02-BasicConfig")
|
||||
field(NOVA, "7") # 7 bytes (0..6 corresponds to input A..G)
|
||||
field(FLNK, "$(P)CAN${CH_ID}-SDO02-BasicConfig.PROC") # Send the data
|
||||
}
|
||||
```
|
||||
|
||||
# Testing
|
||||
You can use a virtual can, vcan network for testing:
|
||||
|
||||
## Prerequirements
|
||||
|
||||
Install can utils:
|
||||
```
|
||||
$ git clone https://github.com/linux-can/can-utils
|
||||
$ cd can-utils
|
||||
$ make
|
||||
$ make install
|
||||
```
|
||||
|
||||
## vcan setup
|
||||
Start virt can 0 (vcan0) and candump:
|
||||
```
|
||||
$ sudo modprobe vcan
|
||||
$ sudo ip link add dev vcan0 type vcan
|
||||
$ sudo ip link set up vcan0
|
||||
$ candump vcan0
|
||||
```
|
||||
candump will start to printout the raw can meassages on vcan0 network.
|
||||
|
||||
# Other intressting things
|
||||
|
||||
In future it could be an option to use one of these repos for the CANOpen support:
|
||||
1. https://github.com/CANopenNode/CANopenNode
|
||||
2. https://github.com/CANopenNode/CANopenSocket
|
||||
3. https://github.com/marel-keytech/openCANopen
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ E3_MODULE_VERSION:=master
|
||||
# DEPENDENT MODULE VERSION
|
||||
# For Example,
|
||||
|
||||
ECMC_DEP_VERSION:=6.3.0
|
||||
ASYN_DEP_VERSION:=4.37.0
|
||||
ECMC_DEP_VERSION:=6.3.2
|
||||
ASYN_DEP_VERSION:=4.41.0
|
||||
|
||||
#DEVLIB2_DEP_VERSION:=2.9.0
|
||||
#PCRE_DEP_VERSION:=8.41.0
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
EPICS_BASE:=${HOME}/epics/base-7.0.4
|
||||
|
||||
E3_REQUIRE_NAME:=require
|
||||
E3_REQUIRE_VERSION:=3.3.0
|
||||
E3_REQUIRE_VERSION:=3.4.0
|
||||
|
||||
# The definitions shown below can also be placed in an untracked RELEASE.local
|
||||
-include $(TOP)/../../RELEASE.local
|
||||
|
||||
21
db/ecmcPluginSocketCAN_Com.template
Normal file
21
db/ecmcPluginSocketCAN_Com.template
Normal file
@@ -0,0 +1,21 @@
|
||||
record(ai,"$(P)CAN-Stat-ComErr"){
|
||||
field(DESC, "Communication error")
|
||||
field(PINI, "$(PINI=1)")
|
||||
field(VAL, "0")
|
||||
field(DTYP, "asynInt32")
|
||||
field(INP, "@asyn($(PORT),$(ADDR=0),$(TIMEOUT=1))T_SMP_MS=$(T_SMP_MS=1000)/TYPE=asynInt32/plugin.can.com.error?")
|
||||
field(SCAN, "I/O Intr")
|
||||
}
|
||||
|
||||
record(bi,"$(P)CAN-Stat-Connected"){
|
||||
field(DESC, "Connected")
|
||||
field(PINI, "$(PINI=1)")
|
||||
field(VAL, "0")
|
||||
field(DTYP, "asynInt32")
|
||||
field(INP, "@asyn($(PORT),$(ADDR=0),$(TIMEOUT=1))T_SMP_MS=$(T_SMP_MS=1000)/TYPE=asynInt32/plugin.can.com.connected?")
|
||||
field(SCAN, "I/O Intr")
|
||||
field(ZNAM, "Not connected")
|
||||
field(ONAM, "Connected")
|
||||
field(ZSV, "MAJOR")
|
||||
field(OSV, "NO_ALARM")
|
||||
}
|
||||
53
db/ecmcPluginSocketCAN_Dev.template
Normal file
53
db/ecmcPluginSocketCAN_Dev.template
Normal file
@@ -0,0 +1,53 @@
|
||||
|
||||
# Device NMT state
|
||||
|
||||
#enum ecmc_nmt_state_act {
|
||||
# NMT_NOT_VALID = 0,
|
||||
# NMT_BOOT_UP = 1,
|
||||
# NMT_STOPPED = 2,
|
||||
# NMT_OP = 3,
|
||||
# NMT_PREOP = 4
|
||||
#};
|
||||
# plugin.can.dev0.nmtstate
|
||||
|
||||
record(mbbi,"$(P)CAN${CH_ID}-Stat-NMT"){
|
||||
field(DESC, "Device NMT state")
|
||||
field(PINI, "$(PINI=1)")
|
||||
field(VAL, "0")
|
||||
field(DTYP, "asynUInt32Digital")
|
||||
field(INP, "@asynMask($(PORT),$(ADDR=0),$(MASK=0xFFFFFFFF),$(TIMEOUT=1))T_SMP_MS=$(T_SMP_MS=1000)/TYPE=asynUInt32Digital/plugin.can.dev${DEV_ID}.nmtstate?")
|
||||
field(SCAN, "I/O Intr")
|
||||
field(TSE, "$(TSE=-2)")
|
||||
field(ZRST,"NMT_NOT_VALID")
|
||||
field(ONST,"NMT_BOOT_UP")
|
||||
field(TWST,"NMT_STOPPED")
|
||||
field(THST,"NMT_OP")
|
||||
field(FRST,"NMT_PREOP")
|
||||
field(FVST,"NMT_NOT_VALID")
|
||||
field(SXST,"NMT_NOT_VALID")
|
||||
field(SVST,"NMT_NOT_VALID")
|
||||
field(EIST,"NMT_NOT_VALID")
|
||||
field(NIST,"NMT_NOT_VALID")
|
||||
field(TEST,"NMT_NOT_VALID")
|
||||
field(ELST,"NMT_NOT_VALID")
|
||||
field(TVST,"NMT_NOT_VALID")
|
||||
field(TTST,"NMT_NOT_VALID")
|
||||
field(FTST,"NMT_NOT_VALID")
|
||||
field(FFST,"NMT_NOT_VALID")
|
||||
field(ZRVL,"0x0")
|
||||
field(ONVL,"0x1")
|
||||
field(TWVL,"0x2")
|
||||
field(THVL,"0x3")
|
||||
field(FRVL,"0x4")
|
||||
field(FVVL,"0x5")
|
||||
field(SXVL,"0x6")
|
||||
field(SVVL,"0x7")
|
||||
field(EIVL,"0x8")
|
||||
field(NIVL,"0x9")
|
||||
field(TEVL,"0xA")
|
||||
field(ELVL,"0xB")
|
||||
field(TVVL,"0xC")
|
||||
field(TTVL,"0xD")
|
||||
field(FTVL,"0xE")
|
||||
field(FFVL,"0xF")
|
||||
}
|
||||
8
db/ecmcPluginSocketCAN_PDO_error.template
Normal file
8
db/ecmcPluginSocketCAN_PDO_error.template
Normal file
@@ -0,0 +1,8 @@
|
||||
record(ai,"$(P)CAN${CH_ID}-${suffix=}"){
|
||||
field(DESC, "SDO error code")
|
||||
field(PINI, "$(PINI=1)")
|
||||
field(VAL, "0")
|
||||
field(DTYP, "asynInt32")
|
||||
field(INP, "@asyn($(PORT),$(ADDR=0),$(TIMEOUT=1))T_SMP_MS=$(T_SMP_MS=1000)/TYPE=asynInt32/plugin.can.dev${DEV_ID}.pdo.${source=value}.error?")
|
||||
field(SCAN, "I/O Intr")
|
||||
}
|
||||
12
db/ecmcPluginSocketCAN_PDO_input.template
Normal file
12
db/ecmcPluginSocketCAN_PDO_input.template
Normal file
@@ -0,0 +1,12 @@
|
||||
# Data source: plugin.can.dev3.pdo.testvalue
|
||||
record(waveform,"$(P)CAN${CH_ID}-${suffix=}"){
|
||||
info(asyn:READBACK,"1")
|
||||
field(DESC, "PDO Data")
|
||||
field(PINI, "1")
|
||||
field(DTYP, "${DTYP=asynInt8ArrayIn}")
|
||||
field(INP, "@asyn(${PORT},$(ADDR=0),$(TIMEOUT=1000))T_SMP_MS=$(T_SMP_MS=1000)/TYPE=${DTYP=asynInt8ArrayIn}/plugin.can.dev${DEV_ID}.pdo.${source=value}?")
|
||||
field(FTVL, "${FTVL=CHAR}")
|
||||
field(NELM, "${NELM=1024}")
|
||||
field(SCAN, "I/O Intr")
|
||||
field(TSE, "0")
|
||||
}
|
||||
12
db/ecmcPluginSocketCAN_PDO_output.template
Normal file
12
db/ecmcPluginSocketCAN_PDO_output.template
Normal file
@@ -0,0 +1,12 @@
|
||||
# Data source: plugin.can.dev3.pdo.testvalue
|
||||
record(waveform,"$(P)CAN${CH_ID}-${suffix=}"){
|
||||
info(asyn:READBACK,"1")
|
||||
field(DESC, "PDO Data")
|
||||
# field(PINI, "1")
|
||||
field(DTYP, "${DTYP=asynInt8ArrayOut}")
|
||||
field(INP, "@asyn(${PORT},$(ADDR=0),$(TIMEOUT=1000))T_SMP_MS=$(T_SMP_MS=1000)/TYPE=${DTYP=asynInt8ArrayOut}/plugin.can.dev${DEV_ID}.pdo.${source=value}=")
|
||||
field(FTVL, "${FTVL=CHAR}")
|
||||
field(NELM, "${NELM=1024}")
|
||||
field(SCAN, "Passive")
|
||||
field(TSE, "0")
|
||||
}
|
||||
8
db/ecmcPluginSocketCAN_SDO_error.template
Normal file
8
db/ecmcPluginSocketCAN_SDO_error.template
Normal file
@@ -0,0 +1,8 @@
|
||||
record(ai,"$(P)CAN${CH_ID}-${suffix=}"){
|
||||
field(DESC, "SDO error code")
|
||||
field(PINI, "$(PINI=1)")
|
||||
field(VAL, "0")
|
||||
field(DTYP, "asynInt32")
|
||||
field(INP, "@asyn($(PORT),$(ADDR=0),$(TIMEOUT=1))T_SMP_MS=$(T_SMP_MS=1000)/TYPE=asynInt32/plugin.can.dev${DEV_ID}.sdo.${source=value}.error?")
|
||||
field(SCAN, "I/O Intr")
|
||||
}
|
||||
12
db/ecmcPluginSocketCAN_SDO_input.template
Normal file
12
db/ecmcPluginSocketCAN_SDO_input.template
Normal file
@@ -0,0 +1,12 @@
|
||||
# Data source: plugin.can.dev3.sdo.analogValues4
|
||||
record(waveform,"$(P)CAN${CH_ID}-${suffix=}"){
|
||||
info(asyn:READBACK,"1")
|
||||
field(DESC, "SDO Data")
|
||||
field(PINI, "1")
|
||||
field(DTYP, "${DTYP=asynInt8ArrayIn}")
|
||||
field(INP, "@asyn(${PORT},$(ADDR=0),$(TIMEOUT=1000))T_SMP_MS=$(T_SMP_MS=1000)/TYPE=${DTYP=asynInt8ArrayIn}/plugin.can.dev${DEV_ID}.sdo.${source=value}?")
|
||||
field(FTVL, "${FTVL=CHAR}")
|
||||
field(NELM, "${NELM=1024}")
|
||||
field(SCAN, "I/O Intr")
|
||||
field(TSE, "0")
|
||||
}
|
||||
11
db/ecmcPluginSocketCAN_SDO_output.template
Normal file
11
db/ecmcPluginSocketCAN_SDO_output.template
Normal file
@@ -0,0 +1,11 @@
|
||||
# Data source: plugin.can.dev3.sdo.analogValues4
|
||||
record(waveform,"$(P)CAN${CH_ID}-${suffix=}"){
|
||||
info(asyn:READBACK,"1")
|
||||
field(DESC, "SDO Data")
|
||||
field(DTYP, "${DTYP=asynInt8ArrayOut}")
|
||||
field(INP, "@asyn(${PORT},$(ADDR=0),$(TIMEOUT=1000))T_SMP_MS=$(T_SMP_MS=1000)/TYPE=${DTYP=asynInt8ArrayOut}/plugin.can.dev${DEV_ID}.sdo.${source=value}=")
|
||||
field(FTVL, "${FTVL=CHAR}")
|
||||
field(NELM, "${NELM=1024}")
|
||||
field(SCAN, "Passive")
|
||||
field(TSE, "0")
|
||||
}
|
||||
13
db/test.template
Normal file
13
db/test.template
Normal file
@@ -0,0 +1,13 @@
|
||||
|
||||
|
||||
record(acalcout,"$(P)LeftMoveData_"){
|
||||
field(DESC, "Move data from EL3702 to EL4732")
|
||||
field(INAA, "$(P)ec$(MASTER_ID)-s$(SLAVE_AI)-EL3702_s$(NELM)-AI1-Array.VAL")
|
||||
field(INPA, "$(P)LeftGain.VAL")
|
||||
field(INPB, "$(P)LeftOffset.VAL")
|
||||
field(INPC, "$(P)Volume.VAL")
|
||||
field(CALC, "(C/200*A/100*AA+(B*3276.8))")
|
||||
field(OUT, "$(P)ec$(MASTER_ID)-s$(SLAVE_AO)-EL4732_s$(NELM)-AO1-Array.VAL")
|
||||
field(FLNK, "$(P)ec$(MASTER_ID)-s$(SLAVE_AO)-EL4732_s$(NELM)-AO1-Array.PROC")
|
||||
field(NELM, "$(NELM)")
|
||||
}
|
||||
2
dbd/ecmc_socketcan.dbd
Normal file
2
dbd/ecmc_socketcan.dbd
Normal file
@@ -0,0 +1,2 @@
|
||||
function(ecmcWriteArrayUint8ElementsInit)
|
||||
function(ecmcWriteArrayUint8Elements)
|
||||
@@ -304,4 +304,542 @@ r 0x583 [8] 0x60 0x90 0x26 0x01 0x00 0x00 0x00 0x00 # Ok from slave
|
||||
w 0x603 [8] 0x60 0x08 0xF4 0xA9 0xBE 0xC0 0xCB 0xE1 # Send data
|
||||
r 0x583 [8] 0x01 0x18 0x00 0x24 0x00 0x0E 0x00 0x0A # ???
|
||||
|
||||
# Check some pvs
|
||||
camonitor IOC_TEST:CAN03-PWR_A IOC_TEST:CAN03-PWR_B IOC_TEST:CAN03-PWR_OUT IOC_TEST:CAN03-REFL_OUT IOC_TEST:CAN03-SDO01-Array IOC_TEST:CAN03-PDO01-Array IOC_TEST:CAN03-PWR_A
|
||||
|
||||
|
||||
|
||||
camonitor IOC_TEST:CAN03-PWR_A IOC_TEST:CAN03-PWR_B IOC_TEST:CAN03-PWR_OUT IOC_TEST:CAN03-REFL_OUT IOC_TEST:CAN03-V_REG IOC_TEST:CAN03-V_TEMP IOC_TEST:CAN03-I_DRV IOC_TEST:CAN03-I_PRE IOC_TEST:CAN03-I_1A IOC_TEST:CAN03-I_2A IOC_TEST:CAN03-V_REFL_SAVE IOC_TEST:CAN03-V_PLUSMON IOC_TEST:CAN03-V_I_DC IOC_TEST:CAN03-I_1B IOC_TEST:CAN03-I_2B IOC_TEST:CAN03-V_12V_MON IOC_TEST:CAN03-VREF_PWR_OPV IOC_TEST:CAN03-V_AUX_IN IOC_TEST:CAN03-V_5V_ACB IOC_TEST:CAN03-V_3V5 IOC_TEST:CAN03-AIR_INLET IOC_TEST:CAN03-AIR_OUTLET IOC_TEST:CAN03-SDO01-Array IOC_TEST:CAN03-PDO01-Array
|
||||
|
||||
|
||||
|
||||
# Most recent data dump
|
||||
w 0x080 [0]
|
||||
w 0x700 [1] 0x05
|
||||
r 0x280 [0]
|
||||
r 0x703 [1] 0x05
|
||||
r 0x280 [0]
|
||||
w 0x7E5 [0]
|
||||
w 0x080 [0]
|
||||
w 0x700 [1] 0x05
|
||||
r 0x280 [0]
|
||||
r 0x280 [0]
|
||||
w 0x7E5 [0]
|
||||
w 0x080 [0]
|
||||
w 0x700 [1] 0x05
|
||||
r 0x280 [0]
|
||||
r 0x703 [1] 0x05
|
||||
r 0x280 [0]
|
||||
w 0x7E5 [0]
|
||||
w 0x080 [0]
|
||||
w 0x700 [1] 0x05
|
||||
r 0x280 [0]
|
||||
r 0x280 [0]
|
||||
w 0x7E5 [0]
|
||||
w 0x080 [0]
|
||||
w 0x700 [1] 0x05
|
||||
r 0x280 [0]
|
||||
r 0x703 [1] 0x05
|
||||
r 0x280 [0]
|
||||
w 0x7E5 [0]STATE = READ_REQ_TRANSFER analogValues
|
||||
STATE = READ_WAIT_FOR_CONF analogValues
|
||||
|
||||
w 0x080 [0]
|
||||
w 0x700 [1] 0x05
|
||||
w 0x603 [8] 0x40 0x40 0x26 0x00 0x00 0x00 0x00 0x00
|
||||
STATE = READ_WAIT_FOR_DATA analogValues
|
||||
r 0x583 [8] 0x41 0x40 0x26 0x00 0x38 0x00 0x00 0x00
|
||||
w 0x603 [8] 0x61 0x40 0x26 0x00 0x00 0x00 0x00 0x00
|
||||
recived bytes = 7
|
||||
r 0x583 [8] 0x00 0x18 0x00 0x24 0x00 0x0E 0x00 0x0A
|
||||
w 0x603 [8] 0x71 0x40 0x26 0x00 0x00 0x00 0x00 0x00
|
||||
recived bytes = 14
|
||||
r 0x583 [8] 0x10 0x00 0x00 0x00 0xC5 0x00 0x00 0x00
|
||||
w 0x603 [8] 0x61 0x40 0x26 0x00 0x00 0x00 0x00 0x00
|
||||
recived bytes = 21
|
||||
r 0x583 [8] 0x00 0xC2 0x01 0x00 0x00 0x00 0x00 0x35
|
||||
w 0x603 [8] 0x71 0x40 0x26 0x00 0x00 0x00 0x00 0x00
|
||||
recived bytes = 28
|
||||
r 0x583 [8] 0x10 0x1C 0x84 0x02 0x46 0x1A 0x3C 0x49
|
||||
w 0x603 [8] 0x61 0x40 0x26 0x00 0x00 0x00 0x00 0x00
|
||||
recived bytes = 35
|
||||
r 0x583 [8] 0x00 0xC2 0x01 0x00 0x00 0x00 0x00 0x00
|
||||
w 0x603 [8] 0x71 0x40 0x26 0x00 0x00 0x00 0x00 0x00
|
||||
recived bytes = 42
|
||||
r 0x583 [8] 0x10 0x00 0xC8 0x48 0x51 0x2F 0x00 0x00
|
||||
w 0x603 [8] 0x61 0x40 0x26 0x00 0x00 0x00 0x00 0x00
|
||||
recived bytes = 49
|
||||
r 0x583 [8] 0x00 0x5C 0x2D 0x7F 0x14 0x67 0x0D 0xAA
|
||||
w 0x603 [8] 0x71 0x40 0x26 0x00 0x00 0x00 0x00 0x00
|
||||
recived bytes = 56
|
||||
STATE = READ_IDLE analogValues
|
||||
All data read from slave SDO.
|
||||
data[00]: 24
|
||||
data[01]: 36
|
||||
data[02]: 14
|
||||
data[03]: 10
|
||||
data[04]: 0
|
||||
data[05]: 197
|
||||
data[06]: 0
|
||||
data[07]: 450
|
||||
data[08]: 0
|
||||
data[09]: 0
|
||||
data[10]: 7221
|
||||
data[11]: 644
|
||||
data[12]: 6726
|
||||
data[13]: 18748
|
||||
data[14]: 450
|
||||
data[15]: 0
|
||||
data[16]: 0
|
||||
data[17]: 0
|
||||
data[18]: 18632
|
||||
data[19]: 12113
|
||||
data[20]: 0
|
||||
data[21]: 11612
|
||||
data[22]: 5247
|
||||
data[23]: 3431
|
||||
data[24]: 170
|
||||
data[25]: 193
|
||||
data[26]: 0
|
||||
data[27]: 5000
|
||||
r 0x583 [8] 0x11 0x00 0xC1 0x00 0x00 0x00 0x88 0x13
|
||||
r 0x280 [0]
|
||||
r 0x683 [4] 0x00 0x00 0x00 0x00
|
||||
r 0x280 [0]
|
||||
w 0x7E5 [0]
|
||||
w 0x080 [0]
|
||||
w 0x700 [1] 0x05
|
||||
r 0x280 [0]
|
||||
data[00]: 0
|
||||
data[01]: 0
|
||||
data[02]: 16395
|
||||
data[03]: 8196
|
||||
r 0x183 [8] 0x00 0x00 0x00 0x00 0x0B 0x40 0x04 0x20
|
||||
r 0x703 [1] 0x05
|
||||
r 0x280 [0]
|
||||
w 0x7E5 [0]
|
||||
w 0x080 [0]
|
||||
w 0x700 [1] 0x05
|
||||
r 0x280 [0]
|
||||
r 0x280 [0]
|
||||
w 0x7E5 [0]
|
||||
w 0x080 [0]
|
||||
w 0x700 [1] 0x05
|
||||
r 0x280 [0]
|
||||
r 0x703 [1] 0x05
|
||||
r 0x280 [0]
|
||||
w 0x7E5 [0]
|
||||
w 0x080 [0]
|
||||
w 0x700 [1] 0x05
|
||||
r 0x280 [0]
|
||||
r 0x280 [0]
|
||||
w 0x7E5 [0]
|
||||
w 0x080 [0]
|
||||
w 0x700 [1] 0x05
|
||||
r 0x280 [0]
|
||||
r 0x703 [1] 0x05
|
||||
r 0x280 [0]
|
||||
w 0x7E5 [0]
|
||||
w 0x080 [0]
|
||||
w 0x700 [1] 0x05
|
||||
r 0x280 [0]
|
||||
r 0x280 [0]
|
||||
w 0x7E5 [0]
|
||||
w 0x080 [0]
|
||||
w 0x700 [1] 0x05
|
||||
STATE = READ_REQ_TRANSFER analogValues
|
||||
STATE = READ_WAIT_FOR_CONF analogValues
|
||||
w 0x603 [8] 0x40 0x40 0x26 0x00 0x00 0x00 0x00 0x00
|
||||
STATE = READ_WAIT_FOR_DATA analogValues
|
||||
r 0x583 [8] 0x41 0x40 0x26 0x00 0x38 0x00 0x00 0x00
|
||||
w 0x603 [8] 0x61 0x40 0x26 0x00 0x00 0x00 0x00 0x00
|
||||
recived bytes = 7
|
||||
r 0x583 [8] 0x00 0x18 0x00 0x24 0x00 0x0E 0x00 0x0A
|
||||
w 0x603 [8] 0x71 0x40 0x26 0x00 0x00 0x00 0x00 0x00
|
||||
recived bytes = 14
|
||||
r 0x583 [8] 0x10 0x00 0x00 0x00 0xC5 0x00 0x00 0x00
|
||||
w 0x603 [8] 0x61 0x40 0x26 0x00 0x00 0x00 0x00 0x00
|
||||
recived bytes = 21
|
||||
r 0x583 [8] 0x00 0xC2 0x01 0x00 0x00 0x00 0x00 0x35
|
||||
w 0x603 [8] 0x71 0x40 0x26 0x00 0x00 0x00 0x00 0x00
|
||||
recived bytes = 28
|
||||
r 0x583 [8] 0x10 0x1C 0x84 0x02 0x46 0x1A 0x3C 0x49
|
||||
w 0x603 [8] 0x61 0x40 0x26 0x00 0x00 0x00 0x00 0x00
|
||||
recived bytes = 35
|
||||
r 0x583 [8] 0x00 0xC2 0x01 0x00 0x00 0x00 0x00 0x00
|
||||
w 0x603 [8] 0x71 0x40 0x26 0x00 0x00 0x00 0x00 0x00
|
||||
recived bytes = 42
|
||||
r 0x583 [8] 0x10 0x00 0xC8 0x48 0x51 0x2F 0x00 0x00
|
||||
w 0x603 [8] 0x61 0x40 0x26 0x00 0x00 0x00 0x00 0x00
|
||||
recived bytes = 49
|
||||
r 0x583 [8] 0x00 0x5C 0x2D 0x7F 0x14 0x67 0x0D 0xAA
|
||||
w 0x603 [8] 0x71 0x40 0x26 0x00 0x00 0x00 0x00 0x00
|
||||
recived bytes = 56
|
||||
STATE = READ_IDLE analogValues
|
||||
All data read from slave SDO.
|
||||
data[00]: 24
|
||||
data[01]: 36
|
||||
data[02]: 14
|
||||
data[03]: 10
|
||||
data[04]: 0
|
||||
data[05]: 197
|
||||
data[06]: 0
|
||||
data[07]: 450
|
||||
data[08]: 0
|
||||
data[09]: 0
|
||||
data[10]: 7221
|
||||
data[11]: 644
|
||||
data[12]: 6726
|
||||
data[13]: 18748
|
||||
data[14]: 450
|
||||
data[15]: 0
|
||||
data[16]: 0
|
||||
data[17]: 0
|
||||
data[18]: 18632
|
||||
data[19]: 12113
|
||||
data[20]: 0
|
||||
data[21]: 11612
|
||||
data[22]: 5247
|
||||
data[23]: 3431
|
||||
data[24]: 170
|
||||
data[25]: 193
|
||||
data[26]: 0
|
||||
data[27]: 5000
|
||||
r 0x583 [8] 0x11 0x00 0xC1 0x00 0x00 0x00 0x88 0x13
|
||||
r 0x280 [0]
|
||||
r 0x280 [0]
|
||||
r 0x703 [1] 0x05
|
||||
w 0x7E5 [0]
|
||||
w 0x080 [0]
|
||||
w 0x700 [1] 0x05
|
||||
r 0x280 [0]
|
||||
r 0x280 [0]
|
||||
w 0x7E5 [0]
|
||||
w 0x080 [0]
|
||||
w 0x700 [1] 0x05
|
||||
r 0x280 [0]
|
||||
r 0x280 [0]
|
||||
r 0x703 [1] 0x05
|
||||
w 0x7E5 [0]
|
||||
w 0x080 [0]
|
||||
w 0x700 [1] 0x05
|
||||
r 0x280 [0]
|
||||
r 0x683 [4] 0x00 0x00 0x00 0x00
|
||||
r 0x280 [0]
|
||||
w 0x7E5 [0]
|
||||
w 0x080 [0]
|
||||
w 0x700 [1] 0x05
|
||||
r 0x280 [0]
|
||||
data[00]: 0
|
||||
data[01]: 0
|
||||
data[02]: 16395
|
||||
data[03]: 8196
|
||||
r 0x183 [8] 0x00 0x00 0x00 0x00 0x0B 0x40 0x04 0x20
|
||||
r 0x703 [1] 0x05
|
||||
r 0x280 [0]
|
||||
w 0x7E5 [0]
|
||||
w 0x080 [0]
|
||||
w 0x700 [1] 0x05
|
||||
r 0x280 [0]
|
||||
r 0x280 [0]
|
||||
w 0x7E5 [0]
|
||||
w 0x080 [0]
|
||||
w 0x700 [1] 0x05
|
||||
r 0x280 [0]
|
||||
r 0x703 [1] 0x05
|
||||
r 0x280 [0]
|
||||
w 0x7E5 [0]
|
||||
w 0x080 [0]
|
||||
w 0x700 [1] 0x05
|
||||
STATE = READ_REQ_TRANSFER analogValues
|
||||
STATE = READ_WAIT_FOR_CONF analogValues
|
||||
w 0x603 [8] 0x40 0x40 0x26 0x00 0x00 0x00 0x00 0x00
|
||||
STATE = READ_WAIT_FOR_DATA analogValues
|
||||
r 0x583 [8] 0x41 0x40 0x26 0x00 0x38 0x00 0x00 0x00
|
||||
w 0x603 [8] 0x61 0x40 0x26 0x00 0x00 0x00 0x00 0x00
|
||||
recived bytes = 7
|
||||
r 0x583 [8] 0x00 0x18 0x00 0x24 0x00 0x0E 0x00 0x0A
|
||||
w 0x603 [8] 0x71 0x40 0x26 0x00 0x00 0x00 0x00 0x00
|
||||
recived bytes = 14
|
||||
r 0x583 [8] 0x10 0x00 0x00 0x00 0xC5 0x00 0x00 0x00
|
||||
w 0x603 [8] 0x61 0x40 0x26 0x00 0x00 0x00 0x00 0x00
|
||||
recived bytes = 21
|
||||
r 0x583 [8] 0x00 0xC2 0x01 0x00 0x00 0x00 0x00 0x35
|
||||
w 0x603 [8] 0x71 0x40 0x26 0x00 0x00 0x00 0x00 0x00
|
||||
recived bytes = 28
|
||||
r 0x583 [8] 0x10 0x1C 0x84 0x02 0x46 0x1A 0x3C 0x49
|
||||
w 0x603 [8] 0x61 0x40 0x26 0x00 0x00 0x00 0x00 0x00
|
||||
recived bytes = 35
|
||||
r 0x583 [8] 0x00 0xC2 0x01 0x00 0x00 0x00 0x00 0x00
|
||||
w 0x603 [8] 0x71 0x40 0x26 0x00 0x00 0x00 0x00 0x00
|
||||
recived bytes = 42
|
||||
r 0x583 [8] 0x10 0x00 0xC8 0x48 0x51 0x2F 0x00 0x00
|
||||
w 0x603 [8] 0x61 0x40 0x26 0x00 0x00 0x00 0x00 0x00
|
||||
recived bytes = 49
|
||||
r 0x583 [8] 0x00 0x5C 0x2D 0x7F 0x14 0x67 0x0D 0xAA
|
||||
w 0x603 [8] 0x71 0x40 0x26 0x00 0x00 0x00 0x00 0x00
|
||||
recived bytes = 56
|
||||
STATE = READ_IDLE analogValues
|
||||
All data read from slave SDO.
|
||||
data[00]: 24
|
||||
data[01]: 36
|
||||
data[02]: 14
|
||||
data[03]: 10
|
||||
data[04]: 0
|
||||
data[05]: 197
|
||||
data[06]: 0
|
||||
data[07]: 450
|
||||
data[08]: 0
|
||||
data[09]: 0
|
||||
data[10]: 7221
|
||||
data[11]: 644
|
||||
data[12]: 6726
|
||||
data[13]: 18748
|
||||
data[14]: 450
|
||||
data[15]: 0
|
||||
data[16]: 0
|
||||
data[17]: 0
|
||||
data[18]: 18632
|
||||
data[19]: 12113
|
||||
data[20]: 0
|
||||
data[21]: 11612
|
||||
data[22]: 5247
|
||||
data[23]: 3431
|
||||
data[24]: 170
|
||||
data[25]: 193
|
||||
data[26]: 0
|
||||
data[27]: 5000
|
||||
r 0x583 [8] 0x11 0x00 0xC1 0x00 0x00 0x00 0x88 0x13
|
||||
r 0x280 [0]
|
||||
r 0x280 [0]
|
||||
w 0x7E5 [0]
|
||||
w 0x080 [0]
|
||||
w 0x700 [1] 0x05
|
||||
r 0x280 [0]
|
||||
r 0x280 [0]
|
||||
r 0x703 [1] 0x05
|
||||
w 0x7E5 [0]
|
||||
w 0x080 [0]
|
||||
w 0x700 [1] 0x05
|
||||
r 0x280 [0]
|
||||
r 0x280 [0]
|
||||
w 0x7E5 [0]
|
||||
w 0x080 [0]
|
||||
w 0x700 [1] 0x05
|
||||
r 0x280 [0]
|
||||
r 0x280 [0]
|
||||
r 0x703 [1] 0x05
|
||||
w 0x7E5 [0]
|
||||
w 0x080 [0]
|
||||
w 0x700 [1] 0x05
|
||||
r 0x280 [0]
|
||||
r 0x280 [0]
|
||||
w 0x7E5 [0]
|
||||
w 0x080 [0]
|
||||
w 0x700 [1] 0x05
|
||||
r 0x280 [0]
|
||||
r 0x280 [0]
|
||||
r 0x703 [1] 0x05
|
||||
w 0x7E5 [0]
|
||||
w 0x080 [0]
|
||||
w 0x700 [1] 0x05
|
||||
r 0x280 [0]
|
||||
r 0x683 [4] 0x00 0x00 0x00 0x00
|
||||
r 0x280 [0]
|
||||
w 0x7E5 [0]
|
||||
w 0x080 [0]
|
||||
w 0x700 [1] 0x05
|
||||
STATE = READ_REQ_TRANSFER analogValues
|
||||
STATE = READ_WAIT_FOR_CONF analogValues
|
||||
w 0x603 [8] 0x40 0x40 0x26 0x00 0x00 0x00 0x00 0x00
|
||||
STATE = READ_WAIT_FOR_DATA analogValues
|
||||
r 0x583 [8] 0x41 0x40 0x26 0x00 0x38 0x00 0x00 0x00
|
||||
w 0x603 [8] 0x61 0x40 0x26 0x00 0x00 0x00 0x00 0x00
|
||||
recived bytes = 7
|
||||
r 0x583 [8] 0x00 0x18 0x00 0x24 0x00 0x0E 0x00 0x0A
|
||||
w 0x603 [8] 0x71 0x40 0x26 0x00 0x00 0x00 0x00 0x00
|
||||
recived bytes = 14
|
||||
r 0x583 [8] 0x10 0x00 0x00 0x00 0xC5 0x00 0x00 0x00
|
||||
w 0x603 [8] 0x61 0x40 0x26 0x00 0x00 0x00 0x00 0x00
|
||||
recived bytes = 21
|
||||
r 0x583 [8] 0x00 0xC2 0x01 0x00 0x00 0x00 0x00 0x35
|
||||
w 0x603 [8] 0x71 0x40 0x26 0x00 0x00 0x00 0x00 0x00
|
||||
recived bytes = 28
|
||||
r 0x583 [8] 0x10 0x1C 0x84 0x02 0x46 0x1A 0x3C 0x49
|
||||
w 0x603 [8] 0x61 0x40 0x26 0x00 0x00 0x00 0x00 0x00
|
||||
recived bytes = 35
|
||||
r 0x583 [8] 0x00 0xC2 0x01 0x00 0x00 0x00 0x00 0x00
|
||||
w 0x603 [8] 0x71 0x40 0x26 0x00 0x00 0x00 0x00 0x00
|
||||
recived bytes = 42
|
||||
r 0x583 [8] 0x10 0x00 0xC8 0x48 0x51 0x2F 0x00 0x00
|
||||
w 0x603 [8] 0x61 0x40 0x26 0x00 0x00 0x00 0x00 0x00
|
||||
recived bytes = 49
|
||||
r 0x583 [8] 0x00 0x5C 0x2D 0x7F 0x14 0x67 0x0D 0xAA
|
||||
w 0x603 [8] 0x71 0x40 0x26 0x00 0x00 0x00 0x00 0x00
|
||||
recived bytes = 56
|
||||
STATE = READ_IDLE analogValues
|
||||
All data read from slave SDO.
|
||||
data[00]: 24
|
||||
data[01]: 36
|
||||
data[02]: 14
|
||||
data[03]: 10
|
||||
data[04]: 0
|
||||
data[05]: 197
|
||||
data[06]: 0
|
||||
data[07]: 450
|
||||
data[08]: 0
|
||||
data[09]: 0
|
||||
data[10]: 7221
|
||||
data[11]: 644
|
||||
data[12]: 6726
|
||||
data[13]: 18748
|
||||
data[14]: 450
|
||||
data[15]: 0
|
||||
data[16]: 0
|
||||
data[17]: 0
|
||||
data[18]: 18632
|
||||
data[19]: 12113
|
||||
data[20]: 0
|
||||
data[21]: 11612
|
||||
data[22]: 5247
|
||||
data[23]: 3431
|
||||
data[24]: 170
|
||||
data[25]: 193
|
||||
data[26]: 0
|
||||
data[27]: 5000
|
||||
r 0x583 [8] 0x11 0x00 0xC1 0x00 0x00 0x00 0x88 0x13
|
||||
r 0x280 [0]
|
||||
data[00]: 0
|
||||
data[01]: 0
|
||||
data[02]: 16395
|
||||
data[03]: 8196
|
||||
r 0x183 [8] 0x00 0x00 0x00 0x00 0x0B 0x40 0x04 0x20
|
||||
r 0x280 [0]
|
||||
r 0x703 [1] 0x05
|
||||
w 0x7E5 [0]
|
||||
w 0x080 [0]
|
||||
w 0x700 [1] 0x05
|
||||
r 0x280 [0]
|
||||
r 0x280 [0]
|
||||
w 0x7E5 [0]
|
||||
w 0x080 [0]
|
||||
w 0x700 [1] 0x05
|
||||
r 0x280 [0]
|
||||
r 0x280 [0]
|
||||
r 0x703 [1] 0x05
|
||||
w 0x7E5 [0]
|
||||
w 0x080 [0]
|
||||
w 0x700 [1] 0x05
|
||||
r 0x280 [0]
|
||||
r 0x280 [0]
|
||||
w 0x7E5 [0]
|
||||
w 0x080 [0]
|
||||
w 0x700 [1] 0x05
|
||||
r 0x280 [0]
|
||||
r 0x280 [0]
|
||||
r 0x703 [1] 0x05
|
||||
w 0x7E5 [0]
|
||||
w 0x080 [0]
|
||||
w 0x700 [1] 0x05
|
||||
|
||||
# Basic configuration write (maybe should write a 4800 (=48%) to byte 3 and 4)
|
||||
caput -a IOC_TEST:CAN03-SDO02-BasicConfig 7 0 0 0 0 0 0
|
||||
# or better 48000 VDC_CTRL (48%), fan stops of pmu and amplifier on is LED is not on anymore
|
||||
caput -a IOC_TEST:CAN03-SDO02-BasicConfig 7 0 0 0 192 18 0 0
|
||||
|
||||
This will happen:
|
||||
WRITEVALUE basicConfig
|
||||
STATE = WRITE_REQ_TRANSFER basicConfig
|
||||
STATE = WRITE_WAIT_FOR_CONF basicConfig
|
||||
w 0x603 [8] 0x21 0x90 0x26 0x01 0x07 0x00 0x00 0x00
|
||||
writeDataStateMachine basicConfig
|
||||
STATE = WRITE_DATA basicConfig
|
||||
r 0x583 [8] 0x60 0x90 0x26 0x01 0x00 0x00 0x00 0x00
|
||||
w 0x603 [8] 0x01 0x00 0x00 0x00 0xC0 0x12 0x00 0x00
|
||||
writeDataStateMachine basicConfig
|
||||
STATE = WRITE_IDLE basicConfig
|
||||
All data written to slave SDO.
|
||||
|
||||
# Amplifier on is bit 0 of byte 0, so to power on just execute:
|
||||
caput -a IOC_TEST:CAN03-SDO02-BasicConfig 7 1 0 0 192 18 0 0
|
||||
|
||||
|
||||
|
||||
# After basic configuration the r 280 will stop () this means the pmu now have basic configuration
|
||||
r 0x583 [8] 0x11 0x00 0xC5 0x00 0x00 0x00 0x88 0x13
|
||||
r 0x703 [1] 0x05
|
||||
w 0x7E5 [0]
|
||||
w 0x080 [0]
|
||||
w 0x700 [1] 0x05
|
||||
w 0x7E5 [0]
|
||||
w 0x080 [0]
|
||||
w 0x700 [1] 0x05
|
||||
r 0x703 [1] 0x05
|
||||
w 0x7E5 [0]
|
||||
w 0x080 [0]
|
||||
w 0x700 [1] 0x05
|
||||
w 0x7E5 [0]
|
||||
w 0x080 [0]
|
||||
w 0x700 [1] 0x05
|
||||
|
||||
|
||||
# Some commands
|
||||
IOC_TEST:CAN03-PowerOnCmd
|
||||
IOC_TEST:CAN03-VrefPwrCmd
|
||||
IOC_TEST:CAN03-VdcCtrlCmd
|
||||
|
||||
## Turn amplifier on
|
||||
caput IOC_TEST:CAN03-PowerOnCmd 1
|
||||
|
||||
WRITEVALUE basicConfig
|
||||
STATE = WRITE_REQ_TRANSFER basicConfig
|
||||
STATE = WRITE_WAIT_FOR_CONF basicConfig
|
||||
w 0x603 [8] 0x21 0x90 0x26 0x01 0x07 0x00 0x00 0x00
|
||||
writeDataStateMachine basicConfig
|
||||
STATE = WRITE_DATA basicConfig
|
||||
r 0x583 [8] 0x60 0x90 0x26 0x01 0x00 0x00 0x00 0x00
|
||||
w 0x603 [8] 0x01 0x01 0x00 0x00 0x00 0x00 0x00 0x00
|
||||
writeDataStateMachine basicConfig
|
||||
STATE = WRITE_IDLE basicConfig
|
||||
All data written to slave SDO.
|
||||
data[00]: 1
|
||||
data[01]: 0
|
||||
data[02]: 0
|
||||
data[03]: 0
|
||||
|
||||
|
||||
## Set VrefOwr to 100
|
||||
caput IOC_TEST:CAN03-VrefPwrCmd 100
|
||||
|
||||
NOT WORKING.. Will get the below???
|
||||
|
||||
r 0x583 [8] 0x11 0x00 0xC5 0x00 0x00 0x00 0x88 0x13
|
||||
WRITEVALUE basicConfig
|
||||
STATE = WRITE_REQ_TRANSFER basicConfig
|
||||
STATE = WRITE_WAIT_FOR_CONF basicConfig
|
||||
w 0x603 [8] 0x21 0x90 0x26 0x01 0x07 0x00 0x00 0x00
|
||||
writeDataStateMachine basicConfig
|
||||
STATE = WRITE_DATA basicConfig
|
||||
r 0x583 [8] 0x60 0x90 0x26 0x01 0x00 0x00 0x00 0x00
|
||||
w 0x603 [8] 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00
|
||||
writeDataStateMachine basicConfig
|
||||
STATE = WRITE_IDLE basicConfig
|
||||
All data written to slave SDO.
|
||||
|
||||
## Set VdcCtrlCmd to 5000
|
||||
caput IOC_TEST:CAN03-VdcCtrlCmd 5000
|
||||
|
||||
NOT WORKING.. Will get the below???
|
||||
|
||||
WRITEVALUE basicConfig
|
||||
STATE = WRITE_REQ_TRANSFER basicConfig
|
||||
STATE = WRITE_WAIT_FOR_CONF basicConfig
|
||||
w 0x603 [8] 0x21 0x90 0x26 0x01 0x07 0x00 0x00 0x00
|
||||
writeDataStateMachine basicConfig
|
||||
STATE = WRITE_DATA basicConfig
|
||||
r 0x583 [8] 0x60 0x90 0x26 0x01 0x00 0x00 0x00 0x00
|
||||
w 0x603 [8] 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00
|
||||
writeDataStateMachine basicConfig
|
||||
STATE = WRITE_IDLE basicConfig
|
||||
All data written to slave SDO.
|
||||
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
#
|
||||
# Copyright (c) 2019 European Spallation Source ERIC
|
||||
#
|
||||
# The program is free software: you can redistribute
|
||||
# it and/or modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation, either version 2 of the
|
||||
# License, or any newer version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# this program. If not, see https://www.gnu.org/licenses/gpl-2.0.txt
|
||||
#
|
||||
#
|
||||
# Author : anderssandstrom
|
||||
# email : anderssandstrom@esss.se
|
||||
# Date : 2020Mar22-1607-33CET
|
||||
# version : 0.0.0
|
||||
#
|
||||
# template file is generated by ./e3TemplateGenerator.bash with bf03d40
|
||||
# Please look at many other _module_.Makefile in e3-* repository
|
||||
#
|
||||
|
||||
## The following lines are mandatory, please don't change them.
|
||||
where_am_I := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
|
||||
include $(E3_REQUIRE_TOOLS)/driver.makefile
|
||||
include $(E3_REQUIRE_CONFIG)/DECOUPLE_FLAGS
|
||||
|
||||
ifneq ($(strip $(ASYN_DEP_VERSION)),)
|
||||
asyn_VERSION=$(ASYN_DEP_VERSION)
|
||||
endif
|
||||
|
||||
ifneq ($(strip $(ECMC_DEP_VERSION)),)
|
||||
ecmc_VERSION=$(ECMC_DEP_VERSION)
|
||||
endif
|
||||
|
||||
APP:=ecmc_plugin_socketcanApp
|
||||
APPDB:=$(APP)/Db
|
||||
APPSRC:=$(APP)/src
|
||||
|
||||
USR_CFLAGS += -shared -fPIC -Wall -Wextra
|
||||
USR_LDFLAGS += -lstdc++
|
||||
USR_INCLUDES += -I$(where_am_I)$(APPSRC)
|
||||
|
||||
TEMPLATES += $(wildcard $(APPDB)/*.db)
|
||||
TEMPLATES += $(wildcard $(APPDB)/*.template)
|
||||
SOURCES += $(APPSRC)/ecmcPluginSocketCAN.c
|
||||
SOURCES += $(APPSRC)/ecmcSocketCAN.cpp
|
||||
SOURCES += $(APPSRC)/ecmcSocketCANWrap.cpp
|
||||
SOURCES += $(APPSRC)/ecmcSocketCANWriteBuffer.cpp
|
||||
SOURCES += $(APPSRC)/ecmcCANOpenSDO.cpp
|
||||
SOURCES += $(APPSRC)/ecmcCANOpenPDO.cpp
|
||||
|
||||
db:
|
||||
|
||||
.PHONY: db
|
||||
|
||||
vlibs:
|
||||
|
||||
.PHONY: vlibs
|
||||
|
||||
###
|
||||
@@ -1,65 +0,0 @@
|
||||
#
|
||||
# Copyright (c) 2019 European Spallation Source ERIC
|
||||
#
|
||||
# The program is free software: you can redistribute
|
||||
# it and/or modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation, either version 2 of the
|
||||
# License, or any newer version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# this program. If not, see https://www.gnu.org/licenses/gpl-2.0.txt
|
||||
#
|
||||
#
|
||||
# Author : anderssandstrom
|
||||
# email : anderssandstrom@esss.se
|
||||
# Date : 2020Mar22-1607-33CET
|
||||
# version : 0.0.0
|
||||
#
|
||||
# template file is generated by ./e3TemplateGenerator.bash with bf03d40
|
||||
# Please look at many other _module_.Makefile in e3-* repository
|
||||
#
|
||||
|
||||
## The following lines are mandatory, please don't change them.
|
||||
where_am_I := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
|
||||
include $(E3_REQUIRE_TOOLS)/driver.makefile
|
||||
include $(E3_REQUIRE_CONFIG)/DECOUPLE_FLAGS
|
||||
|
||||
ifneq ($(strip $(ASYN_DEP_VERSION)),)
|
||||
asyn_VERSION=$(ASYN_DEP_VERSION)
|
||||
endif
|
||||
|
||||
ifneq ($(strip $(ECMC_DEP_VERSION)),)
|
||||
ecmc_VERSION=$(ECMC_DEP_VERSION)
|
||||
endif
|
||||
|
||||
APP:=ecmc_plugin_socketcanApp
|
||||
APPDB:=$(APP)/Db
|
||||
APPSRC:=$(APP)/src
|
||||
|
||||
USR_CFLAGS += -shared -fPIC -Wall -Wextra
|
||||
USR_LDFLAGS += -lstdc++
|
||||
USR_INCLUDES += -I$(where_am_I)$(APPSRC)
|
||||
|
||||
TEMPLATES += $(wildcard $(APPDB)/*.db)
|
||||
TEMPLATES += $(wildcard $(APPDB)/*.template)
|
||||
SOURCES += $(APPSRC)/ecmcPluginSocketCAN.c
|
||||
SOURCES += $(APPSRC)/ecmcSocketCAN.cpp
|
||||
SOURCES += $(APPSRC)/ecmcSocketCANWrap.cpp
|
||||
SOURCES += $(APPSRC)/ecmcSocketCANWriteBuffer.cpp
|
||||
SOURCES += $(APPSRC)/ecmcCANOpenSDO.cpp
|
||||
SOURCES += $(APPSRC)/ecmcCANOpenPDO.cpp
|
||||
|
||||
db:
|
||||
|
||||
.PHONY: db
|
||||
|
||||
vlibs:
|
||||
|
||||
.PHONY: vlibs
|
||||
|
||||
###
|
||||
Submodule ecmc_plugin_socketcan/ecmc_plugin_socketcanApp/src/CANopenNode deleted from 2853bafd27
Submodule ecmc_plugin_socketcan/ecmc_plugin_socketcanApp/src/CANopenSocket deleted from 98d26ed184
@@ -1,140 +0,0 @@
|
||||
/*************************************************************************\
|
||||
* 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.
|
||||
*
|
||||
* ecmcCANOpenPDO.cpp
|
||||
*
|
||||
* Created on: Mar 22, 2020
|
||||
* Author: anderssandstrom
|
||||
* Credits to https://github.com/sgreg/dynamic-loading
|
||||
*
|
||||
\*************************************************************************/
|
||||
|
||||
// Needed to get headers in ecmc right...
|
||||
#define ECMC_IS_PLUGIN
|
||||
|
||||
#include <sstream>
|
||||
#include "ecmcCANOpenPDO.h"
|
||||
|
||||
/**
|
||||
* ecmc ecmcCANOpenPDO class
|
||||
*/
|
||||
ecmcCANOpenPDO::ecmcCANOpenPDO(ecmcSocketCANWriteBuffer* writeBuffer,
|
||||
uint32_t cobId, // 0x580 + CobId
|
||||
ecmc_can_direction rw,
|
||||
uint32_t ODSize,
|
||||
int readTimeoutMs,
|
||||
int writeCycleMs,
|
||||
int exeSampleTimeMs,
|
||||
int dbgMode) {
|
||||
|
||||
writeBuffer_ = writeBuffer;
|
||||
cobId_ = cobId;
|
||||
ODSize_ = ODSize;
|
||||
|
||||
if(ODSize_ > 8) {
|
||||
ODSize_ = 8;
|
||||
}
|
||||
|
||||
readTimeoutMs_ = readTimeoutMs;
|
||||
writeCycleMs_ = writeCycleMs;
|
||||
exeSampleTimeMs_ = exeSampleTimeMs;
|
||||
rw_ = rw;
|
||||
exeCounter_ = 0;
|
||||
busy_ = 0;
|
||||
errorCode_ = 0;
|
||||
dataBuffer_ = new uint8_t(ODSize_);
|
||||
dbgMode_ = dbgMode;
|
||||
|
||||
writeFrame_.can_id = cobId_;
|
||||
writeFrame_.can_dlc = ODSize; // data length
|
||||
writeFrame_.data[0] = 0; // request read cmd
|
||||
writeFrame_.data[1] = 0;
|
||||
writeFrame_.data[2] = 0;
|
||||
writeFrame_.data[3] = 0;
|
||||
writeFrame_.data[4] = 0;
|
||||
writeFrame_.data[5] = 0;
|
||||
writeFrame_.data[6] = 0;
|
||||
writeFrame_.data[7] = 0;
|
||||
|
||||
}
|
||||
|
||||
ecmcCANOpenPDO::~ecmcCANOpenPDO() {
|
||||
delete[] dataBuffer_;
|
||||
}
|
||||
|
||||
void ecmcCANOpenPDO::execute() {
|
||||
|
||||
exeCounter_++;
|
||||
|
||||
if(rw_ == DIR_READ) {
|
||||
if(exeCounter_* exeSampleTimeMs_ >= readTimeoutMs_) {
|
||||
errorCode_ = ECMC_CAN_ERROR_PDO_TIMEOUT;
|
||||
if(dbgMode_) {
|
||||
printf("ECMC_CAN_ERROR_PDO_TIMEOUT (0x%x)\n",errorCode_);
|
||||
}
|
||||
exeCounter_ = 0;
|
||||
}
|
||||
}
|
||||
else { //DIR_WRITE
|
||||
if(writeCycleMs_<=0) { // Only write on demand if cycle is less than 0
|
||||
exeCounter_ = 0;
|
||||
return;
|
||||
}
|
||||
if(exeCounter_* exeSampleTimeMs_ >= writeCycleMs_) {
|
||||
writeValue(); // write in defined cycle
|
||||
exeCounter_ = 0;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// new rx frame recived!
|
||||
void ecmcCANOpenPDO::newRxFrame(can_frame *frame) {
|
||||
// Wait for:
|
||||
if(rw_ == DIR_READ) {
|
||||
if(validateFrame(frame)) {
|
||||
memset(dataBuffer_,0,ODSize_);
|
||||
memcpy(dataBuffer_, &(frame->data[0]),frame->can_dlc);
|
||||
errorCode_ = 0;
|
||||
if(dbgMode_) {
|
||||
printBuffer();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ecmcCANOpenPDO::printBuffer() {
|
||||
if(!dataBuffer_) {
|
||||
return;
|
||||
}
|
||||
|
||||
for(uint32_t i = 0; i < ODSize_; i = i + 2) {
|
||||
uint16_t test;
|
||||
memcpy(&test,&dataBuffer_[i],2);
|
||||
printf("data[%02d]: %u\n",i/2,test);
|
||||
}
|
||||
}
|
||||
|
||||
// r 0x183 [8] 0x00 0x00 0x00 0x00 0x0B 0x40 0x04 0x20
|
||||
int ecmcCANOpenPDO::validateFrame(can_frame *frame) {
|
||||
if(frame->can_id != cobId_) {
|
||||
return 0;
|
||||
}
|
||||
if(frame->can_dlc != ODSize_) {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void ecmcCANOpenPDO::setValue(uint64_t data) {
|
||||
memcpy(dataBuffer_, &data, ODSize_);
|
||||
}
|
||||
|
||||
int ecmcCANOpenPDO::writeValue() {
|
||||
if(writeFrame_.can_dlc > 0) {
|
||||
memcpy(&(writeFrame_.data[0]), dataBuffer_ ,writeFrame_.can_dlc);
|
||||
}
|
||||
return writeBuffer_->addWriteCAN(&writeFrame_);
|
||||
}
|
||||
@@ -1,592 +0,0 @@
|
||||
/*************************************************************************\
|
||||
* 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.
|
||||
*
|
||||
* ecmcSocketCAN.cpp
|
||||
*
|
||||
* Created on: Mar 22, 2020
|
||||
* Author: anderssandstrom
|
||||
* Credits to https://github.com/sgreg/dynamic-loading
|
||||
*
|
||||
\*************************************************************************/
|
||||
|
||||
// Needed to get headers in ecmc right...
|
||||
#define ECMC_IS_PLUGIN
|
||||
|
||||
#define ECMC_PLUGIN_ASYN_PREFIX "plugin.can"
|
||||
#define ECMC_PLUGIN_ASYN_ENABLE "enable"
|
||||
|
||||
#include <sstream>
|
||||
#include "ecmcSocketCAN.h"
|
||||
#include "ecmcPluginClient.h"
|
||||
#include "ecmcAsynPortDriver.h"
|
||||
#include "ecmcAsynPortDriverUtils.h"
|
||||
#include "epicsThread.h"
|
||||
|
||||
// Start worker for socket read()
|
||||
void f_worker_read(void *obj) {
|
||||
if(!obj) {
|
||||
printf("%s/%s:%d: Error: Worker read thread ecmcSocketCAN object NULL..\n",
|
||||
__FILE__, __FUNCTION__, __LINE__);
|
||||
return;
|
||||
}
|
||||
ecmcSocketCAN * canObj = (ecmcSocketCAN*)obj;
|
||||
canObj->doReadWorker();
|
||||
}
|
||||
|
||||
// Start worker for socket connect()
|
||||
void f_worker_connect(void *obj) {
|
||||
if(!obj) {
|
||||
printf("%s/%s:%d: Error: Worker connect thread ecmcSocketCAN object NULL..\n",
|
||||
__FILE__, __FUNCTION__, __LINE__);
|
||||
return;
|
||||
}
|
||||
ecmcSocketCAN * canObj = (ecmcSocketCAN*)obj;
|
||||
canObj->doConnectWorker();
|
||||
}
|
||||
|
||||
/** ecmc ecmcSocketCAN class
|
||||
* This object can throw:
|
||||
* - bad_alloc
|
||||
* - invalid_argument
|
||||
* - runtime_error
|
||||
*/
|
||||
ecmcSocketCAN::ecmcSocketCAN(char* configStr,
|
||||
char* portName,
|
||||
int exeSampleTimeMs)
|
||||
: asynPortDriver(portName,
|
||||
1, /* maxAddr */
|
||||
asynInt32Mask | asynFloat64Mask | asynFloat32ArrayMask |
|
||||
asynFloat64ArrayMask | asynEnumMask | asynDrvUserMask |
|
||||
asynOctetMask | asynInt8ArrayMask | asynInt16ArrayMask |
|
||||
asynInt32ArrayMask | asynUInt32DigitalMask, /* Interface mask */
|
||||
asynInt32Mask | asynFloat64Mask | asynFloat32ArrayMask |
|
||||
asynFloat64ArrayMask | asynEnumMask | asynDrvUserMask |
|
||||
asynOctetMask | asynInt8ArrayMask | asynInt16ArrayMask |
|
||||
asynInt32ArrayMask | asynUInt32DigitalMask, /* Interrupt mask */
|
||||
ASYN_CANBLOCK , /*NOT ASYN_MULTI_DEVICE*/
|
||||
1, /* Autoconnect */
|
||||
0, /* Default priority */
|
||||
0) /* Default stack size */ {
|
||||
// Init
|
||||
cfgCanIFStr_ = NULL;
|
||||
cfgDbgMode_ = 0;
|
||||
cfgAutoConnect_ = 1;
|
||||
destructs_ = 0;
|
||||
socketId_ = -1;
|
||||
connected_ = 0;
|
||||
writeBuffer_ = NULL;
|
||||
testSdo_ = NULL;
|
||||
testPdo_ = NULL;
|
||||
lssPdo_ = NULL;
|
||||
syncPdo_ = NULL;
|
||||
heartPdo_ = NULL;
|
||||
basicConfSdo_ = NULL;
|
||||
cycleCounter_ = 0;
|
||||
|
||||
exeSampleTimeMs_ = exeSampleTimeMs;
|
||||
|
||||
memset(&ifr_,0,sizeof(struct ifreq));
|
||||
memset(&rxmsg_,0,sizeof(struct can_frame));
|
||||
memset(&addr_,0,sizeof(struct sockaddr_can));
|
||||
|
||||
parseConfigStr(configStr); // Assigns all configs
|
||||
// Check valid nfft
|
||||
if(!cfgCanIFStr_ ) {
|
||||
throw std::out_of_range("CAN inteface must be defined (can0, vcan0...).");
|
||||
}
|
||||
|
||||
// Create worker thread for reading socket
|
||||
std::string threadname = "ecmc." ECMC_PLUGIN_ASYN_PREFIX".read";
|
||||
if(epicsThreadCreate(threadname.c_str(), 0, 32768, f_worker_read, this) == NULL) {
|
||||
throw std::runtime_error("Error: Failed create worker thread for read().");
|
||||
}
|
||||
|
||||
// Create worker thread for connecting socket
|
||||
threadname = "ecmc." ECMC_PLUGIN_ASYN_PREFIX".connect";
|
||||
if(epicsThreadCreate(threadname.c_str(), 0, 32768, f_worker_connect, this) == NULL) {
|
||||
throw std::runtime_error("Error: Failed create worker thread for connect().");
|
||||
}
|
||||
|
||||
if(cfgAutoConnect_) {
|
||||
connectPrivate();
|
||||
}
|
||||
writeBuffer_ = new ecmcSocketCANWriteBuffer(socketId_, cfgDbgMode_);
|
||||
testSdo_ = new ecmcCANOpenSDO( writeBuffer_, 0x583,0x603,DIR_READ,0x2640,0,56,7000,exeSampleTimeMs_, cfgDbgMode_);
|
||||
testPdo_ = new ecmcCANOpenPDO( writeBuffer_, 0x183,DIR_READ,8,10000,0,exeSampleTimeMs_, cfgDbgMode_);
|
||||
|
||||
|
||||
// Test LSS heartbeat "master" signal. This makes the led on pmu905 to go to "Normal Communication"
|
||||
// can0 0x7E5 [0]
|
||||
lssPdo_ = new ecmcCANOpenPDO( writeBuffer_, 0x7E5,DIR_WRITE,0,0,1000,exeSampleTimeMs_, cfgDbgMode_);
|
||||
|
||||
|
||||
// Test sync signal
|
||||
// can0 0x80 [0]
|
||||
syncPdo_ = new ecmcCANOpenPDO( writeBuffer_, 0x80,DIR_WRITE,0,0,1000,exeSampleTimeMs_, cfgDbgMode_);
|
||||
|
||||
// Test heartbeat signal
|
||||
// can0 0x701 [1] 05
|
||||
//can_add_write(1793,1,5,0,0,0,0,0,0,0);
|
||||
heartPdo_ = new ecmcCANOpenPDO( writeBuffer_, 0x701,DIR_WRITE,1,0,1000,exeSampleTimeMs_, cfgDbgMode_);
|
||||
heartPdo_->setValue(5);
|
||||
|
||||
basicConfSdo_ = new ecmcCANOpenSDO( writeBuffer_, 0x583,0x603,DIR_WRITE,0x2690,1,7,0,exeSampleTimeMs_, cfgDbgMode_);
|
||||
//byte0 = 0
|
||||
//byte1 = 0
|
||||
//byte2 = 0
|
||||
//byte 3,4 = 5000
|
||||
//byte 5 =0
|
||||
//byte 6 =0
|
||||
//byte 7 =0
|
||||
// => 0x1388000
|
||||
uint64_t tempVal = 0x1388000;
|
||||
uint8_t * val = (uint8_t*)&tempVal;
|
||||
basicConfSdo_->setValue(val,7);
|
||||
initAsyn();
|
||||
}
|
||||
|
||||
ecmcSocketCAN::~ecmcSocketCAN() {
|
||||
// kill worker
|
||||
destructs_ = 1; // maybe need todo in other way..
|
||||
doWriteEvent_.signal();
|
||||
doConnectEvent_.signal();
|
||||
}
|
||||
|
||||
void ecmcSocketCAN::parseConfigStr(char *configStr) {
|
||||
|
||||
// check config parameters
|
||||
if (configStr && configStr[0]) {
|
||||
char *pOptions = strdup(configStr);
|
||||
char *pThisOption = pOptions;
|
||||
char *pNextOption = pOptions;
|
||||
|
||||
while (pNextOption && pNextOption[0]) {
|
||||
pNextOption = strchr(pNextOption, ';');
|
||||
if (pNextOption) {
|
||||
*pNextOption = '\0'; /* Terminate */
|
||||
pNextOption++; /* Jump to (possible) next */
|
||||
}
|
||||
|
||||
// ECMC_PLUGIN_DBG_PRINT_OPTION_CMD (1/0)
|
||||
if (!strncmp(pThisOption, ECMC_PLUGIN_DBG_PRINT_OPTION_CMD, strlen(ECMC_PLUGIN_DBG_PRINT_OPTION_CMD))) {
|
||||
pThisOption += strlen(ECMC_PLUGIN_DBG_PRINT_OPTION_CMD);
|
||||
cfgDbgMode_ = atoi(pThisOption);
|
||||
}
|
||||
|
||||
// ECMC_PLUGIN_CONNECT_OPTION_CMD (1/0)
|
||||
if (!strncmp(pThisOption, ECMC_PLUGIN_CONNECT_OPTION_CMD, strlen(ECMC_PLUGIN_CONNECT_OPTION_CMD))) {
|
||||
pThisOption += strlen(ECMC_PLUGIN_DBG_PRINT_OPTION_CMD);
|
||||
cfgAutoConnect_ = atoi(pThisOption);
|
||||
}
|
||||
|
||||
// ECMC_PLUGIN_IF_OPTION_CMD (Source string)
|
||||
else if (!strncmp(pThisOption, ECMC_PLUGIN_IF_OPTION_CMD, strlen(ECMC_PLUGIN_IF_OPTION_CMD))) {
|
||||
pThisOption += strlen(ECMC_PLUGIN_IF_OPTION_CMD);
|
||||
cfgCanIFStr_=strdup(pThisOption);
|
||||
}
|
||||
|
||||
pThisOption = pNextOption;
|
||||
}
|
||||
free(pOptions);
|
||||
}
|
||||
if(!cfgCanIFStr_) {
|
||||
throw std::invalid_argument( "CAN interface not defined.");
|
||||
}
|
||||
}
|
||||
|
||||
// For connect commands over asyn or plc. let worker connect
|
||||
void ecmcSocketCAN::connectExternal() {
|
||||
if(!connected_) {
|
||||
doConnectEvent_.signal(); // let worker start
|
||||
}
|
||||
}
|
||||
|
||||
void ecmcSocketCAN::connectPrivate() {
|
||||
|
||||
if((socketId_ = socket(PF_CAN, SOCK_RAW, CAN_RAW)) == -1) {
|
||||
throw std::runtime_error( "Error while opening socket.");
|
||||
return;
|
||||
}
|
||||
|
||||
strcpy(ifr_.ifr_name, cfgCanIFStr_);
|
||||
ioctl(socketId_, SIOCGIFINDEX, &ifr_);
|
||||
|
||||
addr_.can_family = AF_CAN;
|
||||
addr_.can_ifindex = ifr_.ifr_ifindex;
|
||||
|
||||
printf("%s at index %d\n", cfgCanIFStr_, ifr_.ifr_ifindex);
|
||||
|
||||
if(bind(socketId_, (struct sockaddr *)&addr_, sizeof(addr_)) == -1) {
|
||||
throw std::runtime_error( "Error in socket bind.");
|
||||
return;
|
||||
}
|
||||
connected_ = 1;
|
||||
}
|
||||
|
||||
int ecmcSocketCAN::getConnected() {
|
||||
return connected_;
|
||||
}
|
||||
|
||||
// Read socket worker
|
||||
void ecmcSocketCAN::doReadWorker() {
|
||||
|
||||
while(true) {
|
||||
|
||||
if(destructs_) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Wait for new CAN frame
|
||||
|
||||
// TODO MUST CHECK RETRUN VALUE OF READ!!!!!
|
||||
read(socketId_, &rxmsg_, sizeof(rxmsg_));
|
||||
if(testSdo_) {
|
||||
testSdo_->newRxFrame(&rxmsg_);
|
||||
}
|
||||
if(testPdo_) {
|
||||
testPdo_->newRxFrame(&rxmsg_);
|
||||
}
|
||||
if(lssPdo_) {
|
||||
lssPdo_->newRxFrame(&rxmsg_);
|
||||
}
|
||||
if(syncPdo_) {
|
||||
syncPdo_->newRxFrame(&rxmsg_);
|
||||
}
|
||||
|
||||
if(heartPdo_) {
|
||||
heartPdo_->newRxFrame(&rxmsg_);
|
||||
}
|
||||
|
||||
if(basicConfSdo_) {
|
||||
basicConfSdo_->newRxFrame(&rxmsg_);
|
||||
}
|
||||
|
||||
if(cfgDbgMode_) {
|
||||
// Simulate candump printout
|
||||
printf("r 0x%03X", rxmsg_.can_id);
|
||||
printf(" [%d]", rxmsg_.can_dlc);
|
||||
for(int i=0; i<rxmsg_.can_dlc; i++ ) {
|
||||
printf(" 0x%02X", rxmsg_.data[i]);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Connect socket worker
|
||||
void ecmcSocketCAN::doConnectWorker() {
|
||||
|
||||
while(true) {
|
||||
|
||||
if(destructs_) {
|
||||
return;
|
||||
}
|
||||
doConnectEvent_.wait();
|
||||
if(destructs_) {
|
||||
return;
|
||||
}
|
||||
connectPrivate();
|
||||
}
|
||||
}
|
||||
|
||||
int ecmcSocketCAN::getlastWritesError() {
|
||||
if(!writeBuffer_) {
|
||||
return ECMC_CAN_ERROR_WRITE_BUFFER_NULL;
|
||||
}
|
||||
return writeBuffer_->getlastWritesError();
|
||||
}
|
||||
|
||||
int ecmcSocketCAN::addWriteCAN(uint32_t canId,
|
||||
uint8_t len,
|
||||
uint8_t data0,
|
||||
uint8_t data1,
|
||||
uint8_t data2,
|
||||
uint8_t data3,
|
||||
uint8_t data4,
|
||||
uint8_t data5,
|
||||
uint8_t data6,
|
||||
uint8_t data7) {
|
||||
|
||||
if(!writeBuffer_) {
|
||||
return ECMC_CAN_ERROR_WRITE_BUFFER_NULL;
|
||||
}
|
||||
|
||||
writeBuffer_->addWriteCAN(canId,
|
||||
len,
|
||||
data0,
|
||||
data1,
|
||||
data2,
|
||||
data3,
|
||||
data4,
|
||||
data5,
|
||||
data6,
|
||||
data7);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ecmcSocketCAN::execute() {
|
||||
|
||||
if(testSdo_) {
|
||||
testSdo_->execute();
|
||||
}
|
||||
|
||||
if(testPdo_) {
|
||||
testPdo_->execute();
|
||||
}
|
||||
|
||||
if(lssPdo_) {
|
||||
lssPdo_->execute();
|
||||
}
|
||||
|
||||
if(syncPdo_) {
|
||||
syncPdo_->execute();
|
||||
}
|
||||
|
||||
if(heartPdo_) {
|
||||
heartPdo_->execute();
|
||||
}
|
||||
|
||||
cycleCounter_++;
|
||||
if(basicConfSdo_) {
|
||||
basicConfSdo_->execute();
|
||||
if(cycleCounter_ > 10000) {
|
||||
cycleCounter_ = 0;
|
||||
printf("################################### TEST WRITE SDO#############\n");
|
||||
basicConfSdo_->writeValue();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void ecmcSocketCAN::initAsyn() {
|
||||
|
||||
// Add enable "plugin.fft%d.enable"
|
||||
/*std::string paramName =ECMC_PLUGIN_ASYN_PREFIX + to_string(objectId_) +
|
||||
"." + ECMC_PLUGIN_ASYN_ENABLE;
|
||||
|
||||
if( createParam(0, paramName.c_str(), asynParamInt32, &asynEnableId_) != asynSuccess ) {
|
||||
throw std::runtime_error("Failed create asyn parameter enable");
|
||||
}
|
||||
setIntegerParam(asynEnableId_, cfgEnable_);
|
||||
|
||||
// Add rawdata "plugin.fft%d.rawdata"
|
||||
paramName =ECMC_PLUGIN_ASYN_PREFIX + to_string(objectId_) +
|
||||
"." + ECMC_PLUGIN_ASYN_RAWDATA;
|
||||
|
||||
if( createParam(0, paramName.c_str(), asynParamFloat64Array, &asynRawDataId_ ) != asynSuccess ) {
|
||||
throw std::runtime_error("Failed create asyn parameter rawdata");
|
||||
}
|
||||
doCallbacksFloat64Array(rawDataBuffer_, cfgNfft_, asynRawDataId_,0);
|
||||
|
||||
// Add rawdata "plugin.fft%d.preprocdata"
|
||||
paramName =ECMC_PLUGIN_ASYN_PREFIX + to_string(objectId_) +
|
||||
"." + ECMC_PLUGIN_ASYN_PPDATA;
|
||||
|
||||
if( createParam(0, paramName.c_str(), asynParamFloat64Array, &asynPPDataId_ ) != asynSuccess ) {
|
||||
throw std::runtime_error("Failed create asyn parameter preprocdata");
|
||||
}
|
||||
doCallbacksFloat64Array(prepProcDataBuffer_, cfgNfft_, asynPPDataId_,0);
|
||||
|
||||
n
|
||||
|
||||
// Add fft amplitude "plugin.fft%d.fftamplitude"
|
||||
paramName = ECMC_PLUGIN_ASYN_PREFIX + to_string(objectId_) +
|
||||
"." + ECMC_PLUGIN_ASYN_FFT_AMP;
|
||||
|
||||
if( createParam(0, paramName.c_str(), asynParamFloat64Array, &asynFFTAmpId_ ) != asynSuccess ) {
|
||||
throw std::runtime_error("Failed create asyn parameter fftamplitude");
|
||||
}
|
||||
doCallbacksFloat64Array(fftBufferResultAmp_, cfgNfft_/2+1, asynFFTAmpId_,0);
|
||||
|
||||
// Add fft "plugin.fft%d.mode"
|
||||
paramName = ECMC_PLUGIN_ASYN_PREFIX + to_string(objectId_) +
|
||||
"." + ECMC_PLUGIN_ASYN_FFT_MODE;
|
||||
|
||||
if( createParam(0, paramName.c_str(), asynParamInt32, &asynFFTModeId_ ) != asynSuccess ) {
|
||||
throw std::runtime_error("Failed create asyn parameter mode");
|
||||
}
|
||||
setIntegerParam(asynFFTModeId_, (epicsInt32)cfgMode_);
|
||||
|
||||
// Add fft "plugin.fft%d.status"
|
||||
paramName = ECMC_PLUGIN_ASYN_PREFIX + to_string(objectId_) +
|
||||
"." + ECMC_PLUGIN_ASYN_FFT_STAT;
|
||||
|
||||
if( createParam(0, paramName.c_str(), asynParamInt32, &asynFFTStatId_ ) != asynSuccess ) {
|
||||
throw std::runtime_error("Failed create asyn parameter status");
|
||||
}
|
||||
setIntegerParam(asynFFTStatId_, (epicsInt32)status_);
|
||||
|
||||
// Add fft "plugin.fft%d.source"
|
||||
paramName = ECMC_PLUGIN_ASYN_PREFIX + to_string(objectId_) +
|
||||
"." + ECMC_PLUGIN_ASYN_FFT_SOURCE;
|
||||
|
||||
if( createParam(0, paramName.c_str(), asynParamInt8Array, &asynSourceId_ ) != asynSuccess ) {
|
||||
throw std::runtime_error("Failed create asyn parameter source");
|
||||
}
|
||||
doCallbacksInt8Array(cfgCanIFStr_, strlen(cfgCanIFStr_), asynSourceId_,0);
|
||||
|
||||
// Add fft "plugin.fft%d.trigg"
|
||||
paramName = ECMC_PLUGIN_ASYN_PREFIX + to_string(objectId_) +
|
||||
"." + ECMC_PLUGIN_ASYN_FFT_TRIGG;
|
||||
|
||||
if( createParam(0, paramName.c_str(), asynParamInt32, &asynTriggId_ ) != asynSuccess ) {
|
||||
throw std::runtime_error("Failed create asyn parameter trigg");
|
||||
}
|
||||
setIntegerParam(asynTriggId_, (epicsInt32)triggOnce_);
|
||||
|
||||
// Add fft "plugin.fft%d.fftxaxis"
|
||||
paramName = ECMC_PLUGIN_nSYN_PREFIX + to_string(objectId_) +
|
||||
"." + ECMC_PLUGIN_ASYN_FFT_X_FREQS;
|
||||
|
||||
if( createParam(0, paramName.c_str(), asynParamFloat64Array, &asynFFTXAxisId_ ) != asynSuccess ) {
|
||||
throw std::runtime_error("Failed create asyn parameter xaxisfreqs");
|
||||
}
|
||||
doCallbacksFloat64Array(fftBufferXAxis_,cfgNfft_ / 2 + 1, asynFFTXAxisId_,0);
|
||||
|
||||
// Add fft "plugin.fft%d.nfft"
|
||||
paramName = ECMC_PLUGIN_ASYN_PREFIX + to_string(objectId_) +
|
||||
"." + ECMC_PLUGIN_ASYN_NFFT;
|
||||
|
||||
if( createParam(0, paramName.c_str(), asynParamInt32, &asynNfftId_ ) != asynSuccess ) {
|
||||
throw std::runtime_error("Failed create asyn parameter nfft");
|
||||
}
|
||||
setIntegerParam(asynNfftId_, (epicsInt32)cfgNfft_);
|
||||
|
||||
// Add fft "plugin.fft%d.rate"
|
||||
paramName = ECMC_PLUGIN_ASYN_PREFIX + to_string(objectId_) +
|
||||
"." + ECMC_PLUGIN_ASYN_RATE;
|
||||
|
||||
if( createParam(0, paramName.c_str(), asynParamFloat64, &asynSRateId_ ) != asynSuccess ) {
|
||||
throw std::runtime_error("Failed create asyn parameter rate");
|
||||
}
|
||||
setDoubleParam(asynSRateId_, cfgDataSampleRateHz_);
|
||||
|
||||
// Add fft "plugin.fft%d.buffid"
|
||||
paramName = ECMC_PLUGIN_ASYN_PREFIX + to_string(objectId_) +
|
||||
"." + ECMC_PLUGIN_ASYN_BUFF_ID;
|
||||
|
||||
if( createParam(0, paramName.c_str(), asynParamInt32, &asynElementsInBuffer_ ) != asynSuccess ) {
|
||||
throw std::runtime_error("Failed create asyn parameter trigg");
|
||||
}
|
||||
setIntegerParam(asynElementsInBuffer_, (epicsInt32)elementsInBuffer_);
|
||||
|
||||
// Update integers
|
||||
callParamCallbacks();*/
|
||||
}
|
||||
|
||||
// Avoid issues with std:to_string()
|
||||
std::string ecmcSocketCAN::to_string(int value) {
|
||||
std::ostringstream os;
|
||||
os << value;
|
||||
return os.str();
|
||||
}
|
||||
|
||||
asynStatus ecmcSocketCAN::writeInt32(asynUser *pasynUser, epicsInt32 value) {
|
||||
int function = pasynUser->reason;
|
||||
/*if( function == asynEnableId_ ) {
|
||||
cfgEnable_ = value;
|
||||
return asynSuccess;
|
||||
} else if( function == asynFFTModeId_){
|
||||
cfgMode_ = (FFT_MODE)value;// Called from low prio worker thread. Makes the hard work
|
||||
void ecmcSocketCAN::doCalcWorker() {
|
||||
|
||||
while(true) {
|
||||
doCalcEvent_.wait();
|
||||
if(destructs_) {
|
||||
break;
|
||||
}
|
||||
// Pre-process
|
||||
removeDCOffset(); // Remove dc on rawdata
|
||||
removeLin(); // Remove fitted line
|
||||
// Process
|
||||
calcFFT(); // FFT cacluation
|
||||
// Post-process
|
||||
scaleFFT(); // Scale FFT
|
||||
calcFFTAmp(); // Calculate amplitude from complex
|
||||
calcFFTXAxis(); // Calculate x axis
|
||||
|
||||
doCallbacksFloat64Array(rawDataBuffer_, cfgNfft_, asynRawDataId_, 0);
|
||||
doCallbacksFloat64Array(prepProcDataBuffer_, cfgNfft_, asynPPDataId_, 0);
|
||||
doCallbacksFloat64Array(fftBufferResultAmp_,cfgNfft_/2+1, asynFFTAmpId_, 0);
|
||||
doCallbacksFloat64Array(fftBufferXAxis_, cfgNfft_/2+1, asynFFTXAxisId_,0);
|
||||
callParamCallbacks();
|
||||
if(cfgDbgMode_){
|
||||
printComplexArray(fftBufferResult_,
|
||||
cfgNfft_,
|
||||
objectId_);
|
||||
printEcDataArray((uint8_t*)rawDataBuffer_,
|
||||
cfgNfft_*sizeof(double),
|
||||
ECMC_EC_F64,
|
||||
objectId_);
|
||||
}
|
||||
|
||||
clearBuffers();
|
||||
triggOnce_ = 0; // Wait for next trigger if in trigg mode
|
||||
setIntegerParam(asynTriggId_,triggOnce_);
|
||||
fftWaitingForCalc_ = 0;
|
||||
}
|
||||
}
|
||||
return asynSuccess;
|
||||
}
|
||||
return asynError;*/
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
asynStatus ecmcSocketCAN::readInt32(asynUser *pasynUser, epicsInt32 *value) {
|
||||
int function = pasynUser->reason;
|
||||
/*if( function == asynEnableId_ ) {
|
||||
*value = cfgEnable_;
|
||||
return asynSuccess;
|
||||
} else if( function == asynFFTModeId_ ){
|
||||
*value = cfgMode_;
|
||||
return asynSuccess;
|
||||
} else if( function == asynTriggId_ ){
|
||||
*value = triggOnce_;
|
||||
return asynSuccess;
|
||||
}else if( function == asynFFTStatId_ ){
|
||||
*value = (epicsInt32)status_;
|
||||
return asynSuccess;
|
||||
}else if( function == asynNfftId_ ){
|
||||
*value = (epicsInt32)cfgNfft_;
|
||||
return asynSuccess;
|
||||
}else if( function == asynElementsInBuffer_){
|
||||
*value = (epicsInt32)elementsInBuffer_;
|
||||
return asynSuccess;
|
||||
}
|
||||
return asynError;*/
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
asynStatus ecmcSocketCAN::readInt8Array(asynUser *pasynUser, epicsInt8 *value,
|
||||
size_t nElements, size_t *nIn) {
|
||||
int function = pasynUser->reason;
|
||||
/*if( function == asynSourceId_ ) {
|
||||
unsigned int ncopy = strlen(cfgCanIFStr_);
|
||||
if(nElements < ncopy) {
|
||||
ncopy = nElements;
|
||||
}
|
||||
memcpy (value, cfgCanIFStr_, ncopy);
|
||||
*nIn = ncopy;
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
*nIn = 0;
|
||||
return asynError;*/
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
asynStatus ecmcSocketCAN::readFloat64(asynUser *pasynUser, epicsFloat64 *value) {
|
||||
int function = pasynUser->reason;
|
||||
/*if( function == asynSRateId_ ) {
|
||||
*value = cfgDataSampleRateHz_;
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
return asynError;*/
|
||||
return asynSuccess;
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
/*************************************************************************\
|
||||
* 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.
|
||||
*
|
||||
* ecmcFFTWrap.cpp
|
||||
*
|
||||
* Created on: Mar 22, 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 "ecmcSocketCANWrap.h"
|
||||
#include "ecmcSocketCAN.h"
|
||||
#include "ecmcSocketCANDefs.h"
|
||||
|
||||
#define ECMC_PLUGIN_MAX_PORTNAME_CHARS 64
|
||||
#define ECMC_PLUGIN_PORTNAME_PREFIX "PLUGIN.CAN"
|
||||
|
||||
static ecmcSocketCAN* can = NULL;
|
||||
static char portNameBuffer[ECMC_PLUGIN_MAX_PORTNAME_CHARS];
|
||||
|
||||
int createSocketCAN(char* configStr, int exeSampleTimeMs) {
|
||||
|
||||
// create new ecmcFFT object
|
||||
|
||||
// create asynport name for new object ()
|
||||
memset(portNameBuffer, 0, ECMC_PLUGIN_MAX_PORTNAME_CHARS);
|
||||
snprintf (portNameBuffer, ECMC_PLUGIN_MAX_PORTNAME_CHARS,
|
||||
ECMC_PLUGIN_PORTNAME_PREFIX);
|
||||
try {
|
||||
can = new ecmcSocketCAN(configStr, portNameBuffer, exeSampleTimeMs);
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
if(can) {
|
||||
delete can;
|
||||
}
|
||||
printf("Exception: %s. Plugin will unload.\n",e.what());
|
||||
return ECMC_PLUGIN_SOCKETCAN_ERROR_CODE;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int connectSocketCAN() {
|
||||
if(can){
|
||||
try {
|
||||
can->connectExternal();
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
printf("Exception: %s.\n",e.what());
|
||||
return ECMC_PLUGIN_SOCKETCAN_ERROR_CODE;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return ECMC_PLUGIN_SOCKETCAN_ERROR_CODE;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int getSocketCANConnectd() {
|
||||
if(can){
|
||||
try {
|
||||
return can->getConnected();
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
printf("Exception: %s.\n",e.what());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int getlastWritesError() {
|
||||
if(can){
|
||||
try {
|
||||
return can->getlastWritesError();
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
printf("Exception: %s.\n",e.what());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int execute() {
|
||||
if(can){
|
||||
can->execute();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int addWriteSocketCAN( double canId,
|
||||
double len,
|
||||
double data0,
|
||||
double data1,
|
||||
double data2,
|
||||
double data3,
|
||||
double data4,
|
||||
double data5,
|
||||
double data6,
|
||||
double data7) {
|
||||
if(can){
|
||||
try {
|
||||
return can->addWriteCAN((uint32_t) canId,
|
||||
(uint8_t) len,
|
||||
(uint8_t) data0,
|
||||
(uint8_t) data1,
|
||||
(uint8_t) data2,
|
||||
(uint8_t) data3,
|
||||
(uint8_t) data4,
|
||||
(uint8_t) data5,
|
||||
(uint8_t) data6,
|
||||
(uint8_t) data7);
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
printf("Exception: %s.\n",e.what());
|
||||
return ECMC_PLUGIN_SOCKETCAN_ERROR_CODE;
|
||||
}
|
||||
}
|
||||
return ECMC_PLUGIN_SOCKETCAN_ERROR_CODE;
|
||||
}
|
||||
|
||||
void deleteSocketCAN() {
|
||||
if(can) {
|
||||
delete (can);
|
||||
}
|
||||
}
|
||||
Submodule ecmc_plugin_socketcan/ecmc_plugin_socketcanApp/src/openCANopen deleted from 9880c66650
@@ -19,9 +19,9 @@ static.time:=ec_get_time();
|
||||
# can_add_write(2021,0,0,0,0,0,0,0,0,0);
|
||||
|
||||
#can_trigg_writes();
|
||||
if(can_last_writes_error()) {
|
||||
println('Error during writes : ', can_last_writes_error());
|
||||
};
|
||||
#if(can_last_writes_error()) {
|
||||
# println('Error during writes : ', can_last_writes_error());
|
||||
#};
|
||||
#println('Total time for one plcscan [ms]: ', (ec_get_time()-static.time)/1E6);
|
||||
|
||||
|
||||
@@ -59,34 +59,34 @@ var byte3:=0;
|
||||
#var byte7:= ec_chk_bits(dataLen,24,31);
|
||||
#can_add_write(1539,8,byte0,byte1,byte2,byte3,byte4,byte5,byte6,byte7);
|
||||
|
||||
#can_add_write(1539,8,byte0,byte1,byte2,byte3,0,0,0,0);
|
||||
can_add_write(1539,8,64,byte1,byte2,byte3,0,0,0,0);
|
||||
|
||||
# Acknowledge 1 toggle 0
|
||||
can_add_write(1539,8,97,byte1,byte2,byte3,0,0,0,0);
|
||||
|
||||
# Acknowledge 2 toggle 1
|
||||
can_add_write(1539,8,113,byte1,byte2,byte3,0,0,0,0);
|
||||
|
||||
# Acknowledge 3 toggle 0
|
||||
can_add_write(1539,8,97,byte1,byte2,byte3,0,0,0,0);
|
||||
|
||||
# Acknowledge 4 toggle 1
|
||||
can_add_write(1539,8,113,byte1,byte2,byte3,0,0,0,0);
|
||||
|
||||
# Acknowledge 5 toggle 0
|
||||
can_add_write(1539,8,97,byte1,byte2,byte3,0,0,0,0);
|
||||
|
||||
# Acknowledge 6 toggle 1
|
||||
can_add_write(1539,8,113,byte1,byte2,byte3,0,0,0,0);
|
||||
|
||||
# Acknowledge 7 toggle 0 LAST
|
||||
can_add_write(1539,8,97,byte1,byte2,byte3,0,0,0,0);
|
||||
|
||||
# Acknowledge 6 toggle 1 Test one to much.. just resulted in one more row of unique data?!
|
||||
# can_add_write(1539,8,113,byte1,byte2,byte3,0,0,0,0);
|
||||
|
||||
println('Total time for one plcscan [ms]: ', (ec_get_time()-static.time)/1E6);
|
||||
return [];
|
||||
|
||||
println('NEVER HERE');
|
||||
##can_add_write(1539,8,byte0,byte1,byte2,byte3,0,0,0,0);
|
||||
#can_add_write(1539,8,64,byte1,byte2,byte3,0,0,0,0);
|
||||
#
|
||||
## Acknowledge 1 toggle 0
|
||||
#can_add_write(1539,8,97,byte1,byte2,byte3,0,0,0,0);
|
||||
#
|
||||
## Acknowledge 2 toggle 1
|
||||
#can_add_write(1539,8,113,byte1,byte2,byte3,0,0,0,0);
|
||||
#
|
||||
## Acknowledge 3 toggle 0
|
||||
#can_add_write(1539,8,97,byte1,byte2,byte3,0,0,0,0);
|
||||
#
|
||||
## Acknowledge 4 toggle 1
|
||||
#can_add_write(1539,8,113,byte1,byte2,byte3,0,0,0,0);
|
||||
#
|
||||
## Acknowledge 5 toggle 0
|
||||
#can_add_write(1539,8,97,byte1,byte2,byte3,0,0,0,0);
|
||||
#
|
||||
## Acknowledge 6 toggle 1
|
||||
#can_add_write(1539,8,113,byte1,byte2,byte3,0,0,0,0);
|
||||
#
|
||||
## Acknowledge 7 toggle 0 LAST
|
||||
#can_add_write(1539,8,97,byte1,byte2,byte3,0,0,0,0);
|
||||
#
|
||||
## Acknowledge 6 toggle 1 Test one to much.. just resulted in one more row of unique data?!
|
||||
## can_add_write(1539,8,113,byte1,byte2,byte3,0,0,0,0);
|
||||
#
|
||||
#println('Total time for one plcscan [ms]: ', (ec_get_time()-static.time)/1E6);
|
||||
#return [];
|
||||
#
|
||||
#println('NEVER HERE');
|
||||
|
||||
@@ -1,14 +1,55 @@
|
||||
IOC_TEST:PLC-0-enable
|
||||
REQMOD:mcag-trgt-muts--15408:MODULES
|
||||
REQMOD:mcag-trgt-muts--15408:VERSIONS
|
||||
REQMOD:mcag-trgt-muts--15408:MOD_VER
|
||||
REQMOD:mcag-trgt-muts--15408:exit
|
||||
REQMOD:mcag-trgt-muts--15408:BaseVersion
|
||||
REQMOD:mcag-trgt-muts--15408:require_VER
|
||||
REQMOD:mcag-trgt-muts--15408:ecmccfg_VER
|
||||
REQMOD:mcag-trgt-muts--15408:asyn_VER
|
||||
REQMOD:mcag-trgt-muts--15408:exprtk_VER
|
||||
REQMOD:mcag-trgt-muts--15408:motor_VER
|
||||
REQMOD:mcag-trgt-muts--15408:ecmc_VER
|
||||
IOC_TEST:CAN03-V_TEMP_
|
||||
IOC_TEST:CAN03-AIR_INLET_
|
||||
IOC_TEST:CAN03-AIR_OUTLET_
|
||||
IOC_TEST:CAN03-VrefPwrCmdCalcB1_
|
||||
IOC_TEST:CAN03-VrefPwrCmdCalcB2_
|
||||
IOC_TEST:CAN03-VdcCtrlCmdCalcB3_
|
||||
IOC_TEST:CAN03-VdcCtrlCmdCalcB4_
|
||||
IOC_TEST:CAN03-VrefPwrCmd
|
||||
IOC_TEST:CAN03-VdcCtrlCmd
|
||||
IOC_TEST:CAN03-BasicConfigPackArray_
|
||||
REQMOD:raspberrypi-21050:exit
|
||||
IOC_TEST:PLC-0-scantime
|
||||
IOC_TEST:PLC-0-error
|
||||
IOC_TEST:CAN03-PWR_A
|
||||
IOC_TEST:CAN03-PWR_B
|
||||
IOC_TEST:CAN03-PWR_OUT
|
||||
IOC_TEST:CAN03-REFL_OUT
|
||||
IOC_TEST:CAN03-V_REG
|
||||
IOC_TEST:CAN03-V_TEMP
|
||||
IOC_TEST:CAN03-I_DRV
|
||||
IOC_TEST:CAN03-I_PRE
|
||||
IOC_TEST:CAN03-I_1A
|
||||
IOC_TEST:CAN03-I_2A
|
||||
IOC_TEST:CAN03-V_REFL_SAVE
|
||||
IOC_TEST:CAN03-V_PLUSMON
|
||||
IOC_TEST:CAN03-V_I_DC
|
||||
IOC_TEST:CAN03-I_1B
|
||||
IOC_TEST:CAN03-I_2B
|
||||
IOC_TEST:CAN03-V_12V_MON
|
||||
IOC_TEST:CAN03-VREF_PWR_OPV
|
||||
IOC_TEST:CAN03-V_AUX_IN
|
||||
IOC_TEST:CAN03-V_5V_ACB
|
||||
IOC_TEST:CAN03-V_3V5
|
||||
IOC_TEST:CAN03-AIR_INLET
|
||||
IOC_TEST:CAN03-AIR_OUTLET
|
||||
REQMOD:raspberrypi-21050:BaseVersion
|
||||
REQMOD:raspberrypi-21050:require_VER
|
||||
REQMOD:raspberrypi-21050:ecmccfg_VER
|
||||
REQMOD:raspberrypi-21050:sequencer_VER
|
||||
REQMOD:raspberrypi-21050:sscan_VER
|
||||
REQMOD:raspberrypi-21050:calc_VER
|
||||
REQMOD:raspberrypi-21050:asyn_VER
|
||||
REQMOD:raspberrypi-21050:exprtk_VER
|
||||
REQMOD:raspberrypi-21050:motor_VER
|
||||
REQMOD:raspberrypi-21050:ecmc_VER
|
||||
REQMOD:raspberrypi-21050:ecmc_plugin_socketcan_VER
|
||||
IOC_TEST:CAN03-BasicConfigB0_
|
||||
IOC_TEST:PLC-0-enable
|
||||
IOC_TEST:CAN03-PowerOnCmd
|
||||
REQMOD:raspberrypi-21050:MODULES
|
||||
REQMOD:raspberrypi-21050:VERSIONS
|
||||
REQMOD:raspberrypi-21050:MOD_VER
|
||||
IOC_TEST:CAN03-PDO01-Array
|
||||
IOC_TEST:CAN03-SDO01-Array
|
||||
IOC_TEST:CAN03-SDO02-BasicConfig
|
||||
|
||||
@@ -21,21 +21,90 @@ $(ECMCCFG_INIT)$(SCRIPTEXEC) ${ecmccfg_DIR}startup.cmd, "IOC=$(IOC),ECMC_VER=6.3
|
||||
|
||||
##############################################################################
|
||||
## Load plugin:
|
||||
#require ecmc_plugin_advanced master # do not require then loaded twice..
|
||||
require ecmc_plugin_socketcan master
|
||||
|
||||
epicsEnvSet(ECMC_PLUGIN_FILNAME,"/home/dev/epics/base-7.0.4/require/${E3_REQUIRE_VERSION}/siteMods/ecmc_plugin_socketcan/master/lib/${EPICS_HOST_ARCH=linux-x86_64}/libecmc_plugin_socketcan.so")
|
||||
epicsEnvSet(ECMC_PLUGIN_CONFIG,"IF=can0;DBG_PRINT=1;") # Only one option implemented in this plugin
|
||||
epicsEnvSet(ECMC_PLUGIN_CONFIG,"IF=vcan0;DBG_PRINT=1;") # Only one option implemented in this plugin
|
||||
${SCRIPTEXEC} ${ecmccfg_DIR}loadPlugin.cmd, "PLUGIN_ID=0,FILE=${ECMC_PLUGIN_FILNAME},CONFIG='${ECMC_PLUGIN_CONFIG}', REPORT=1"
|
||||
epicsEnvUnset(ECMC_PLUGIN_FILNAME)
|
||||
epicsEnvUnset(ECMC_PLUGIN_CONFIG)
|
||||
|
||||
##############################################################################
|
||||
## PLC 0
|
||||
$(SCRIPTEXEC) $(ecmccfg_DIR)loadPLCFile.cmd, "PLC_ID=0, SAMPLE_RATE_MS=1000,FILE=./plc/can.plc")
|
||||
# $(SCRIPTEXEC) $(ecmccfg_DIR)loadPLCFile.cmd, "PLC_ID=0, SAMPLE_RATE_MS=1000,FILE=./plc/can.plc")
|
||||
|
||||
##############################################################################
|
||||
############# Prepare virt can for test:
|
||||
|
||||
# Install can utils:
|
||||
# $ git clone https://github.com/linux-can/can-utils
|
||||
# $ cd can-utils
|
||||
# $ make
|
||||
# $ make install
|
||||
#
|
||||
# Start virt can 0 (vcan0) and candump:
|
||||
# $ sudo modprobe vcan
|
||||
# $ sudo ip link add dev vcan0 type vcan
|
||||
# $ sudo ip link set up vcan0
|
||||
# $ candump vcan0
|
||||
|
||||
##############################################################################
|
||||
############# Configure CAN plugin:
|
||||
# Commands:
|
||||
# ecmcCANOpenAddMaster -h
|
||||
# Use ecmcCANOpenAddMaster(<name>, <node id>,....)
|
||||
# <name> : Name of master device.
|
||||
# <node id> : CANOpen node id of master.
|
||||
# <LSS sample time ms> : Sample time for LSS.
|
||||
# <Sync sample time ms> : Sample time for SYNC.
|
||||
# <Heartbeat sample time ms> : Sample time for Heartbeat.
|
||||
#
|
||||
ecmcCANOpenAddMaster("ecmcCANOpenMaster",0,1000,1000,1000)
|
||||
|
||||
# ecmcCANOpenAddDevice -h
|
||||
# Use ecmcCANOpenAddDevice(<name>, <node id>)
|
||||
# <name> : Name of device.
|
||||
# <node id> : CANOpen node id of device.
|
||||
#
|
||||
ecmcCANOpenAddDevice("testDevice",3)
|
||||
|
||||
# ecmcCANOpenAddPDO -h
|
||||
# Use "ecmcCANOpenAddPDO(<name>, <node id>
|
||||
# <name> : Name of master device.
|
||||
# <node id> : CANOpen node id of device/master.
|
||||
# <cob id> : CANOpen cob id of PDO.
|
||||
# <dir> : Direction 1=write and 2=read.
|
||||
# <ODSize> : Size of PDO (max 8 bytes).
|
||||
# <readTimeoutMs> : Readtimeout in ms.
|
||||
# <writeCycleMs> : Cycle time for write (if <= 0 then only write on change).
|
||||
ecmcCANOpenAddPDO("status1",3,0x183,2,8,10000,0) # READ
|
||||
dbLoadRecords(ecmcPluginSocketCAN_PDO_input.template, "P=${ECMC_PREFIX},PORT=${ECMC_ASYN_PORT},ADDR=0,TIMEOUT=1,T_SMP_MS=${ECMC_SAMPLE_RATE_MS},TSE=${ECMC_TSE},NELM=${NELM=1},CH_ID=03,DEV_ID=3,suffix=PDO01-Array,source=status1,NELM=8")
|
||||
|
||||
# ecmcCANOpenAddSDO -h
|
||||
# Use ecmcCANOpenAddSDO(<name>, <node id>,.....)
|
||||
# <name> : Name of master device.
|
||||
# <node id> : CANOpen node id of device/master.
|
||||
# <cob id tx> : CANOpen cob id of Tx of slave SDO.
|
||||
# <cob id rx> : CANOpen cob id of Rx of slave SDO.
|
||||
# <dir> : Direction 1=write and 2=read.
|
||||
# <ODIndex> : OD index of SDO.
|
||||
# <ODSubIndex> : OD sub index of SDO.
|
||||
# <ODSize> : OS Size.
|
||||
# <readTimeoutMs> : Readtimeout in ms.
|
||||
#
|
||||
ecmcCANOpenAddSDO("analogValues1",3,0x583,0x603,2,0x2640,0x0,56,7000) # READ
|
||||
ecmcCANOpenAddSDO("analogValues2",3,0x583,0x603,2,0x2640,0x0,56,7000) # READ
|
||||
ecmcCANOpenAddSDO("analogValues3",3,0x583,0x603,2,0x2640,0x0,56,7000) # READ
|
||||
ecmcCANOpenAddSDO("analogValues4",3,0x583,0x603,2,0x2640,0x0,56,7000) # READ
|
||||
dbLoadTemplate(ecmcPluginSocketCAN_SDO.substitutions, "P=${ECMC_PREFIX},PORT=${ECMC_ASYN_PORT},ADDR=0,TIMEOUT=1,T_SMP_MS=${ECMC_SAMPLE_RATE_MS},TSE=${ECMC_TSE}")
|
||||
|
||||
ecmcCANOpenAddSDO("basicConfig",3,0x583,0x603,1,0x2690,0x1,7,0) # WRITE
|
||||
dbLoadRecords(ecmcPluginSocketCAN_SDO_output.template, "P=${ECMC_PREFIX},PORT=${ECMC_ASYN_PORT},ADDR=0,TIMEOUT=1,T_SMP_MS=${ECMC_SAMPLE_RATE_MS},TSE=${ECMC_TSE},NELM=${NELM=1},CH_ID=03,DEV_ID=3,suffix=BasicConfig,source=basicConfig,DTYP=asynInt8ArrayOut,FTVL=CHAR,NELM=7")
|
||||
##############################################################################
|
||||
############# Go to realtime:
|
||||
|
||||
ecmcConfigOrDie "Cfg.SetAppMode(1)"
|
||||
iocInit
|
||||
dbl > pvs.log
|
||||
|
||||
#ecmcGrepParam *plugin.can.*
|
||||
|
||||
209
kvaser/obsolete/README.md
Normal file
209
kvaser/obsolete/README.md
Normal file
@@ -0,0 +1,209 @@
|
||||
## History
|
||||
Kvaser does not support socketcan for kernel 3.10.
|
||||
However, Kvaser support supplied a custom driver for Kernel 3.10 for testing.
|
||||
|
||||
## Files
|
||||
In this dir you will find two files:
|
||||
|
||||
1. socketcan_kvaser_drivers_for_3.10.tar
|
||||
|
||||
2. socketcan_kvaser_drivers_for_3.10_patched.tar.gz
|
||||
|
||||
The "socketcan_kvaser_drivers_for_3.10.tar" is the "raw" files recived from Kvaser support. This file did not compile. Kvaser suggested to remove some lines in the Makefile. This resulted in the "socketcan_kvaser_drivers_for_3.10_patched.tar.gz" file.
|
||||
|
||||
The "socketcan_kvaser_drivers_for_3.10_patched.tar.gz" driver compiles and installs just fine. But an error is generated when the Kvaser Leaf Light v2 interafce is connected to USB and the interface will not work:
|
||||
|
||||
dmesg using socketcan_kvaser_drivers_for_3.10.tar (with patched Makefile):
|
||||
```
|
||||
Connecting leaf to usb:
|
||||
[Mar24 11:11] usbcore: deregistering interface driver kvaser_usb
|
||||
[Mar24 11:12] usb 1-2: new high-speed USB device number 10 using xhci_hcd
|
||||
[ +0.126425] usb 1-2: New USB device found, idVendor=0bfd, idProduct=0120, bcdDevice= 0.01
|
||||
[ +0.000002] usb 1-2: New USB device strings: Mfr=1, Product=2, SerialNumber=0
|
||||
[ +0.000002] usb 1-2: Product: Kvaser Leaf Light v2
|
||||
[ +0.000002] usb 1-2: Manufacturer: Kvaser AB
|
||||
[ +1.058714] kvaser_usb 1-2:1.0: Cannot get software infos, error -110
|
||||
[ +0.000007] kvaser_usb: probe of 1-2:1.0 failed with error -110
|
||||
[ +0.000027] usbcore: registered new interface driver kvaser_usb
|
||||
|
||||
The interface is not working (not visible with ip addr).
|
||||
|
||||
Disconnecting leaf from usb:
|
||||
[Mar24 11:21] usb 1-2: USB disconnect, device number 10
|
||||
[Mar24 11:23] usbcore: deregistering interface driver kvaser_usb
|
||||
|
||||
```
|
||||
|
||||
## CONCLUSION
|
||||
|
||||
Use file "kvaser/socketcan_kvaser_drivers_1.6.113_patch_asm_3.10.tar.gz"
|
||||
This file contains a patched version of the newest Kvaser socketcan drivers.
|
||||
Basically everything that did not compile was removed, mostly related to CAN FD. A function can_change_state() was also not availble in kernel 3.10. Kvaser support supplied a can_change_state() which have been added to the code.
|
||||
|
||||
So the patched newest version of the Kvaser socketcan drivers is the only option for the time beeing. By using this driver, communiucation to a slave is possible.
|
||||
|
||||
One error is however encounterd during dereg of the device, see below.
|
||||
|
||||
dmesg from when this driver is used:
|
||||
```
|
||||
##########################################################################
|
||||
dmesg using newest Kvaser socketcan_kvaser_drivers patched and added can_change_state()
|
||||
|
||||
Connecting leaf to usb:
|
||||
[Mar24 11:26] usb 1-2: new high-speed USB device number 11 using xhci_hcd
|
||||
[ +0.126349] usb 1-2: New USB device found, idVendor=0bfd, idProduct=0120, bcdDevice= 0.01
|
||||
[ +0.000003] usb 1-2: New USB device strings: Mfr=1, Product=2, SerialNumber=0
|
||||
[ +0.000002] usb 1-2: Product: Kvaser Leaf Light v2
|
||||
[ +0.000002] usb 1-2: Manufacturer: Kvaser AB
|
||||
[ +0.034801] usbcore: registered new interface driver kvaser_usb
|
||||
|
||||
Can intreface is now visible with ip addr:
|
||||
|
||||
ip addr
|
||||
8: can0: <NOARP,ECHO> mtu 16 qdisc noop state DOWN group default qlen 10
|
||||
link/can
|
||||
|
||||
|
||||
And the interface is working!
|
||||
|
||||
Disconnecting leaf from usb:
|
||||
[Mar24 10:31] usb 1-2: USB disconnect, device number 8
|
||||
[ +0.000050] kvaser_usb 1-2:1.0 can0: Cannot flush queue, error -19
|
||||
[ +0.000002] kvaser_usb 1-2:1.0 can0: Cannot reset card, error -19
|
||||
[ +0.000002] kvaser_usb 1-2:1.0 can0: Cannot stop device, error -19
|
||||
|
||||
|
||||
```
|
||||
These error messages are normal when the usb is unplugged while link is up (confirmed with kvaser support).
|
||||
|
||||
So conclusion is that use of "kvaser/socketcan_kvaser_drivers_1.6.113_patch_asm_3.10.tar.gz" is the only way to make it work under kernel 3.10.
|
||||
According to Kvaser support this approach is OK and should work just fine. Also test shows it works well.
|
||||
|
||||
|
||||
# OBSOLETE NOTES BELOW:
|
||||
#### 1. First test without install of driver and without kvaser leaf connected.
|
||||
|
||||
Check support before install of driver:
|
||||
|
||||
```
|
||||
sudo cat /boot/config-3.10.0-1127.el7.x86_64 | grep KVASER
|
||||
CONFIG_CAN_KVASER_PCI=m
|
||||
CONFIG_CAN_KVASER_USB=m
|
||||
```
|
||||
|
||||
Seems support for KVASER_USB already without installing.
|
||||
|
||||
Check ip addr:
|
||||
```
|
||||
ip addr
|
||||
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
|
||||
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
|
||||
inet 127.0.0.1/8 scope host lo
|
||||
valid_lft forever preferred_lft forever
|
||||
inet6 ::1/128 scope host
|
||||
valid_lft forever preferred_lft forever
|
||||
2: eno1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
|
||||
link/ether c0:3f:d5:66:25:44 brd ff:ff:ff:ff:ff:ff
|
||||
inet 172.30.2.34/27 brd 172.30.2.63 scope global noprefixroute dynamic eno1
|
||||
valid_lft 9999557sec preferred_lft 9999557sec
|
||||
inet6 fe80::c23f:d5ff:fe66:2544/64 scope link noprefixroute
|
||||
valid_lft forever preferred_lft forever
|
||||
|
||||
|
||||
```
|
||||
Seems no can interface (and it's not connected).
|
||||
|
||||
#### 2. Connect kvaser leaf:
|
||||
|
||||
Check ip addr:
|
||||
```
|
||||
ip addr
|
||||
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
|
||||
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
|
||||
inet 127.0.0.1/8 scope host lo
|
||||
valid_lft forever preferred_lft forever
|
||||
inet6 ::1/128 scope host
|
||||
valid_lft forever preferred_lft forever
|
||||
2: eno1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
|
||||
link/ether c0:3f:d5:66:25:44 brd ff:ff:ff:ff:ff:ff
|
||||
inet 172.30.2.34/27 brd 172.30.2.63 scope global noprefixroute dynamic eno1
|
||||
valid_lft 9999557sec preferred_lft 9999557sec
|
||||
inet6 fe80::c23f:d5ff:fe66:2544/64 scope link noprefixroute
|
||||
valid_lft forever preferred_lft forever
|
||||
|
||||
|
||||
```
|
||||
Still no can0...
|
||||
|
||||
```
|
||||
dmesg:[ 543.068096] usb 2-4: new high-speed USB device number 4 using xhci_hcd
|
||||
[ 543.191792] usb 2-4: New USB device found, idVendor=0bfd, idProduct=0120, bcdDevice= 0.01
|
||||
[ 543.191803] usb 2-4: New USB device strings: Mfr=1, Product=2, SerialNumber=0
|
||||
[ 543.191809] usb 2-4: Product: Kvaser Leaf Light v2
|
||||
[ 543.191815] usb 2-4: Manufacturer: Kvaser AB
|
||||
|
||||
```
|
||||
|
||||
|
||||
#### 3. Try start services and conf if
|
||||
|
||||
###### Services
|
||||
Start services:
|
||||
```
|
||||
$ sudo modprobe can_dev
|
||||
$ sudo modprobe can
|
||||
$ sudo modprobe can_raw
|
||||
$ sudo ip link set can0 type can bitrate 125000
|
||||
|
||||
```
|
||||
Conclusion:
|
||||
Cannot find can0.. Need to install kvaser driver..
|
||||
|
||||
|
||||
#### Insall kvaser socketcan 3.10
|
||||
|
||||
##### 1 unplug kvaser leaf
|
||||
|
||||
##### 2 untar driver source
|
||||
```
|
||||
cd kvaser
|
||||
tar -xvf socketcan_kvaser_drivers_for_3.10.tar
|
||||
cd socketcan_kvaser_drivers/
|
||||
```
|
||||
|
||||
NOTE: The socketcan_kvaser_drivers_for_3.10.tar was recived on request from kvaser support. Their normal socketcan driver will not support kernel 3.10.
|
||||
|
||||
##### 3 make
|
||||
Read the README file in kvaser driver.
|
||||
These commands are from that README:
|
||||
```
|
||||
$ sudo make uninstall
|
||||
$ make
|
||||
$ sudo make install
|
||||
```
|
||||
|
||||
Fails at make...
|
||||
|
||||
sudo yum install kernel-devel
|
||||
The kernel sources are at the wrong place.. Sources for wrong kernel!!
|
||||
|
||||
Need to update
|
||||
|
||||
```
|
||||
sudo yum update
|
||||
```
|
||||
Now the kernel sources are at the corerct location. patched version of new socketcan version builds and seems to work with candump and connected pmu905
|
||||
|
||||
#### THIS IS A SIDE NOTE for PEAK usb CAN interface !!!
|
||||
|
||||
Check if support for PEAK USB
|
||||
```
|
||||
sudo cat /boot/config-3.10.0-1127.el7.x86_64 | grep PEAK
|
||||
CONFIG_CAN_PEAK_PCI=m
|
||||
CONFIG_CAN_PEAK_PCIEC=y
|
||||
CONFIG_CAN_PEAK_USB=m
|
||||
CONFIG_SND_FIREWIRE_SPEAKERS=m
|
||||
# CONFIG_SPEAKUP is not set
|
||||
```
|
||||
|
||||
|
||||
BIN
kvaser/obsolete/socketcan_kvaser_drivers_for_3.10.tar
Normal file
BIN
kvaser/obsolete/socketcan_kvaser_drivers_for_3.10.tar
Normal file
Binary file not shown.
BIN
kvaser/obsolete/socketcan_kvaser_drivers_for_3.10_patched.tar.gz
Normal file
BIN
kvaser/obsolete/socketcan_kvaser_drivers_for_3.10_patched.tar.gz
Normal file
Binary file not shown.
126
kvaser/readme.md
Normal file
126
kvaser/readme.md
Normal file
@@ -0,0 +1,126 @@
|
||||
# Use Kvaser Leaf Light v2
|
||||
|
||||
## Kernel 3.10
|
||||
|
||||
The socketcan drivers sources downloadable from kvaser webpage will not work for kernel 3.10 but a patched version can be used.
|
||||
|
||||
NOTE: This workflow will only work for the Kvaser Leaf Light v2 (and not the Kvaser Hydra).
|
||||
|
||||
### Pre reqs
|
||||
|
||||
Install kernel headers
|
||||
```
|
||||
sudo yum install kernel-devel
|
||||
```
|
||||
Note: make sure that correct kernel headers are installed for the current kernel, otherwise maybe a update is needed.
|
||||
|
||||
### Install kvaser driver
|
||||
|
||||
#### Use the correct source
|
||||
|
||||
Goto kvaser dir and untar the file: socketcan_kvaser_drivers_1.6.113_patch_asm_3.10.tar.gz
|
||||
|
||||
The "socketcan_kvaser_drivers_1.6.113_patch_asm_3.10.tar.gz" contains a patched version of the kvaser socketcan driver version 1.6.113.
|
||||
Functionality not availbale in kernel 3.10 have been removed. For the driver for the Leaf Light v2 this includes:
|
||||
|
||||
1. Removing CAN FD support
|
||||
|
||||
2. Adding sources provided by kvaser support for the "can_change_state()" function since it is not avilabe in the 3.10 kernel.
|
||||
|
||||
NOTE: This source will only work for Kvaser Leaf Light v2 usb. It will not work for Kvaser Hydra.
|
||||
```
|
||||
cd kvaser
|
||||
tar -xvf socketcan_kvaser_drivers_1.6.113_patch_asm_3.10.tar.gz
|
||||
cd socketcan_kvaser_drivers/
|
||||
```
|
||||
Note: Non patched source downladed from kvaser will not work with kernel 3.10. So use the source in kvaser dir of this repo.
|
||||
|
||||
#### make
|
||||
Read the README file in kvaser driver.
|
||||
These commands are from that README:
|
||||
```
|
||||
# UNPLUGG KVASER USB DEVICE FIRST
|
||||
$ sudo make uninstall
|
||||
$ make
|
||||
$ sudo make install
|
||||
```
|
||||
|
||||
Note: You will get some warnings from hydra source but like stated above the hydra hw or driver is NOT supported and should be avoided. So basically this driver ONLY supports the Kvaser Leaf Light v2.
|
||||
|
||||
### DMESG
|
||||
|
||||
#### Connect Kvaser Leaf Light v2 USB
|
||||
|
||||
dmesg using newest Kvaser socketcan_kvaser_drivers patched and added "can_change_state()" (socketcan_kvaser_drivers_1.6.113_patch_asm_3.10.tar.gz):
|
||||
|
||||
```
|
||||
# Connecting leaf to usb:
|
||||
[Mar24 11:26] usb 1-2: new high-speed USB device number 11 using xhci_hcd
|
||||
[ +0.126349] usb 1-2: New USB device found, idVendor=0bfd, idProduct=0120, bcdDevice= 0.01
|
||||
[ +0.000003] usb 1-2: New USB device strings: Mfr=1, Product=2, SerialNumber=0
|
||||
[ +0.000002] usb 1-2: Product: Kvaser Leaf Light v2
|
||||
[ +0.000002] usb 1-2: Manufacturer: Kvaser AB
|
||||
[ +0.034801] usbcore: registered new interface driver kvaser_usb
|
||||
|
||||
Can interface is now visible with ip addr:
|
||||
|
||||
ip addr
|
||||
8: can0: <NOARP,ECHO> mtu 16 qdisc noop state DOWN group default qlen 10
|
||||
link/can
|
||||
|
||||
```
|
||||
The interface is working and communication to can slave are possible.
|
||||
|
||||
#### Disconnect Kvaser Leaf Light v2 USB
|
||||
|
||||
The following errors will appear in dmesg if unplugging the device while link is up.
|
||||
```
|
||||
# Disconnecting leaf from usb:
|
||||
[Mar24 10:31] usb 1-2: USB disconnect, device number 8
|
||||
[ +0.000050] kvaser_usb 1-2:1.0 can0: Cannot flush queue, error -19
|
||||
[ +0.000002] kvaser_usb 1-2:1.0 can0: Cannot reset card, error -19
|
||||
[ +0.000002] kvaser_usb 1-2:1.0 can0: Cannot stop device, error -19
|
||||
```
|
||||
|
||||
Checked with kvaser support and this is normal when unplugging the usb when the the link is up. So this is not an Issue.
|
||||
|
||||
|
||||
### More info
|
||||
|
||||
More info on other driver supplied by Kvaser can be found here:
|
||||
[Other driver (currently not working) ](obsolete/README.md)
|
||||
|
||||
## Kernel version >= 4.x
|
||||
Go to kvaser webpage and download kvaser socket can drivers. Install according to instructions.
|
||||
|
||||
|
||||
## Start services and configure interface
|
||||
|
||||
First connect the Kvaser Leaf Light v2 usb interface to the controller.
|
||||
|
||||
Start services:
|
||||
```
|
||||
$ sudo modprobe can_dev
|
||||
$ sudo modprobe can
|
||||
$ sudo modprobe can_raw
|
||||
#$ sudo modprobe kvaser_usb # not needed, will start as soon as leaf is plugged in
|
||||
$ sudo ip link set can0 type can bitrate 125000 # 125000 is bitrate (works for pmu905)
|
||||
$ sudo ip link set up can0
|
||||
```
|
||||
Now you should see the can0 interface, test with "ip addr"
|
||||
```
|
||||
ip addr
|
||||
...
|
||||
7: can0: <NOARP,UP,LOWER_UP,ECHO> mtu 16 qdisc pfifo_fast state UNKNOWN group default qlen 10
|
||||
link/can
|
||||
...
|
||||
```
|
||||
|
||||
Check that you have the correct services running (lsmod):
|
||||
```
|
||||
lsmod | grep can
|
||||
can_raw 17120 0
|
||||
can 36567 1 can_raw
|
||||
can_dev 20760 1 kvaser_usb
|
||||
...
|
||||
```
|
||||
BIN
kvaser/socketcan_kvaser_drivers_1.6.113_patch_asm_3.10.tar.gz
Normal file
BIN
kvaser/socketcan_kvaser_drivers_1.6.113_patch_asm_3.10.tar.gz
Normal file
Binary file not shown.
171
src/ecmcByteToArrayAsub.cpp
Normal file
171
src/ecmcByteToArrayAsub.cpp
Normal file
@@ -0,0 +1,171 @@
|
||||
/*************************************************************************\
|
||||
* 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.
|
||||
*
|
||||
* ecmcByteToArrayAsub.cpp
|
||||
*
|
||||
* Created on: Mar 18, 2021
|
||||
* Author: anderssandstrom
|
||||
*
|
||||
* Usage:
|
||||
* 1. Link bytes to inputs A..S.
|
||||
* 2. Link array to VALA.
|
||||
* 3. Set size of output in NOVA. This also defines how many inputs will be used.
|
||||
* Note: Max 18 bytes (input A..S) will be merged into the array.
|
||||
*
|
||||
\*************************************************************************/
|
||||
|
||||
// aSub, EPICS related headers
|
||||
#include <aSubRecord.h>
|
||||
#include <registryFunction.h>
|
||||
#include <epicsExport.h>
|
||||
// std::cout
|
||||
#include <iostream>
|
||||
// split double into fractional and integer
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
// declare init function
|
||||
static long ecmcByteToArrayInit(struct aSubRecord *rec);
|
||||
epicsRegisterFunction(ecmcByteToArrayInit);
|
||||
|
||||
// declare worker function
|
||||
static long ecmcByteToArray(struct aSubRecord *rec);
|
||||
epicsRegisterFunction(ecmcByteToArray);
|
||||
|
||||
// init (INAM)
|
||||
static long ecmcByteToArrayInit(struct aSubRecord *rec){
|
||||
epicsUInt8 byteCount=(epicsUInt8)rec->nova;
|
||||
std::cout << "ecmcByteToArrayInit aSubRecord: "<< rec->name << std::endl;
|
||||
printf("ecmcByteToArray: Bytes to me merged %d\n",(int)byteCount);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long ecmcByteToArray(struct aSubRecord *rec){
|
||||
// input A must be size of output array
|
||||
|
||||
epicsUInt8 byteCount=(epicsUInt8)rec->nova;
|
||||
|
||||
//printf("ecmcByteToArray: Meging %d bytes to an array.\n",(int)byteCount);
|
||||
// Max 18 byte in a row
|
||||
if(byteCount <=0 || byteCount>18){
|
||||
printf("WARNING: Only 18 first bytes will be transferred to output.\n");
|
||||
}
|
||||
|
||||
epicsUInt8 *vala;
|
||||
vala = (epicsUInt8 *)rec->vala;
|
||||
|
||||
if(byteCount >= 1 && rec->noa ==1) {
|
||||
vala[0]=*(epicsUInt8 *)rec->a;
|
||||
}
|
||||
|
||||
if(byteCount >= 2 && rec->nob ==1) {
|
||||
vala[1]=*(epicsUInt8 *)rec->b;
|
||||
}
|
||||
|
||||
if(byteCount >= 3 && rec->noc ==1) {
|
||||
vala[2]=*(epicsUInt8 *)rec->c;
|
||||
}
|
||||
|
||||
if(byteCount >= 4 && rec->nod ==1) {
|
||||
vala[3]=*(epicsUInt8 *)rec->d;
|
||||
}
|
||||
|
||||
if(byteCount >= 5 && rec->noe ==1) {
|
||||
vala[4]=*(epicsUInt8 *)rec->e;
|
||||
}
|
||||
|
||||
if(byteCount >= 6 && rec->nof ==1) {
|
||||
vala[5]=*(epicsUInt8 *)rec->f;
|
||||
}
|
||||
|
||||
if(byteCount >= 7 && rec->nog ==1) {
|
||||
vala[6]=*(epicsUInt8 *)rec->g;
|
||||
}
|
||||
|
||||
if(byteCount >= 8 && rec->noh ==1) {
|
||||
vala[7]=*(epicsUInt8 *)rec->h;
|
||||
}
|
||||
|
||||
if(byteCount >= 9 && rec->noi ==1) {
|
||||
vala[8]=*(epicsUInt8 *)rec->i;
|
||||
}
|
||||
|
||||
if(byteCount >= 10 && rec->noj ==1) {
|
||||
vala[9]=*(epicsUInt8 *)rec->j;
|
||||
}
|
||||
|
||||
if(byteCount >= 11 && rec->nok ==1) {
|
||||
vala[10]=*(epicsUInt8 *)rec->k;
|
||||
}
|
||||
|
||||
if(byteCount >= 12 && rec->nol ==1) {
|
||||
vala[11]=*(epicsUInt8 *)rec->l;
|
||||
}
|
||||
|
||||
if(byteCount >= 13 && rec->nom ==1) {
|
||||
vala[12]=*(epicsUInt8 *)rec->m;
|
||||
}
|
||||
|
||||
if(byteCount >= 13 && rec->non ==1) {
|
||||
vala[13]=*(epicsUInt8 *)rec->n;
|
||||
}
|
||||
|
||||
if(byteCount >= 14 && rec->noo ==1) {
|
||||
vala[14]=*(epicsUInt8 *)rec->o;
|
||||
}
|
||||
|
||||
if(byteCount >= 15 && rec->nop ==1) {
|
||||
vala[15]=*(epicsUInt8 *)rec->p;
|
||||
}
|
||||
|
||||
if(byteCount >= 16 && rec->noq ==1) {
|
||||
vala[16]=*(epicsUInt8 *)rec->q;
|
||||
}
|
||||
|
||||
if(byteCount >= 17 && rec->nor ==1) {
|
||||
vala[17]=*(epicsUInt8 *)rec->r;
|
||||
}
|
||||
|
||||
if(byteCount >= 18 && rec->nos ==1) {
|
||||
vala[18]=*(epicsUInt8 *)rec->s;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
--------------------------------------------------------------------------------
|
||||
EPICS database example
|
||||
record(aSub, "$(P)CAN${CH_ID}-BasicConfigPackArray_") {
|
||||
field(INAM, "ecmcByteToArrayInit")
|
||||
field(SNAM, "ecmcByteToArray")
|
||||
field(FTA, "UCHAR")
|
||||
field(NOA, "1")
|
||||
field(INPA, "$(P)CAN${CH_ID}-BasicConfigB0_.VAL") # Byte 0
|
||||
field(FTB, "UCHAR")
|
||||
field(NOB, "1")
|
||||
field(INPB, "$(P)CAN${CH_ID}-VrefPwrCmdCalcB1_.VAL") # Byte 1
|
||||
field(FTC, "UCHAR")
|
||||
field(NOC, "1")
|
||||
field(INPC, "$(P)CAN${CH_ID}-VrefPwrCmdCalcB2_.VAL") # Byte 2
|
||||
field(FTD, "UCHAR")
|
||||
field(NOD, "1")
|
||||
field(INPD, "$(P)CAN${CH_ID}-VdcCtrlCmdCalcB3_.VAL") # Byte 3
|
||||
field(FTE, "UCHAR")
|
||||
field(NOE, "1")
|
||||
field(INPE, "$(P)CAN${CH_ID}-VdcCtrlCmdCalcB4_.VAL") # Byte 4
|
||||
field(FTF, "UCHAR")
|
||||
field(NOF, "1")
|
||||
field(INPF, "0") # Byte 5
|
||||
field(FTG, "UCHAR")
|
||||
field(NOG, "1")
|
||||
field(INPG, "0") # Byte 6
|
||||
field(FTVA, "UCHAR")
|
||||
field(OUTA, "$(P)CAN${CH_ID}-SDO02-BasicConfig")
|
||||
field(NOVA, "7") # 7 bytes (0..6 corresponds to input A..G)
|
||||
field(FLNK, "$(P)CAN${CH_ID}-SDO02-BasicConfig.PROC") # Send the data
|
||||
}
|
||||
--------------------------------------------------------------------------------
|
||||
*/
|
||||
267
src/ecmcCANOpenDevice.cpp
Normal file
267
src/ecmcCANOpenDevice.cpp
Normal file
@@ -0,0 +1,267 @@
|
||||
/*************************************************************************\
|
||||
* 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.
|
||||
*
|
||||
* ecmcCANOpenDevice.cpp
|
||||
*
|
||||
* Created on: Mar 08, 2021
|
||||
* Author: anderssandstrom
|
||||
*
|
||||
\*************************************************************************/
|
||||
|
||||
// Needed to get headers in ecmc right...
|
||||
#define ECMC_IS_PLUGIN
|
||||
|
||||
#include <sstream>
|
||||
#include "ecmcCANOpenDevice.h"
|
||||
#include "ecmcAsynPortDriver.h"
|
||||
#include "ecmcPluginClient.h"
|
||||
|
||||
/**
|
||||
* ecmc ecmcCANOpenDevice class
|
||||
*/
|
||||
ecmcCANOpenDevice::ecmcCANOpenDevice(ecmcSocketCANWriteBuffer* writeBuffer,
|
||||
uint32_t nodeId, // 0x580 + CobId
|
||||
int exeSampleTimeMs,
|
||||
const char* name,
|
||||
int heartTimeoutMs,
|
||||
int dbgMode) {
|
||||
|
||||
writeBuffer_ = writeBuffer;
|
||||
nodeId_ = nodeId;
|
||||
exeSampleTimeMs_ = exeSampleTimeMs;
|
||||
exeCounter_ = 0;
|
||||
errorCode_ = 0;
|
||||
heartTimeoutMs_ = heartTimeoutMs;
|
||||
dbgMode_ = dbgMode;
|
||||
name_ = strdup(name);
|
||||
isMaster_ = false;
|
||||
nmtState_ = NMT_NOT_VALID;
|
||||
nmtStateOld_ = NMT_NOT_VALID;
|
||||
nmtActParam_ = NULL;
|
||||
heartBeatCounter_ = 0;
|
||||
pdoCounter_ = 0;
|
||||
sdoCounter_ = 0;
|
||||
sdo1Lock_.test_and_set(); // make sure only one sdo is accessing the bus at the same time
|
||||
sdo1Lock_.clear();
|
||||
for(int i = 0 ; i<ECMC_CAN_DEVICE_PDO_MAX_COUNT;i++) {
|
||||
pdos_[i] = NULL;
|
||||
}
|
||||
for(int i = 0 ; i<ECMC_CAN_DEVICE_SDO_MAX_COUNT;i++) {
|
||||
sdos_[i] = NULL;
|
||||
}
|
||||
initAsyn();
|
||||
}
|
||||
|
||||
ecmcCANOpenDevice::~ecmcCANOpenDevice() {
|
||||
for(int i = 0 ; i<ECMC_CAN_DEVICE_PDO_MAX_COUNT;i++) {
|
||||
delete pdos_[i];
|
||||
}
|
||||
for(int i = 0 ; i<ECMC_CAN_DEVICE_SDO_MAX_COUNT;i++) {
|
||||
delete sdos_[i];
|
||||
}
|
||||
|
||||
free(name_);
|
||||
}
|
||||
|
||||
void ecmcCANOpenDevice::execute() {
|
||||
|
||||
exeCounter_++;
|
||||
for(int i=0 ; i<pdoCounter_; i++) {
|
||||
if(pdos_[i]) {
|
||||
pdos_[i]->execute();
|
||||
}
|
||||
}
|
||||
|
||||
for(int i=0 ; i<sdoCounter_; i++) {
|
||||
if(sdos_[i]) {
|
||||
sdos_[i]->execute();
|
||||
}
|
||||
}
|
||||
|
||||
// NMT hearbeat timout
|
||||
if (heartBeatCounter_ * exeSampleTimeMs_ >= heartTimeoutMs_) {
|
||||
nmtStateOld_ = nmtState_;
|
||||
nmtState_ = NMT_NOT_VALID;
|
||||
nmtActParam_->refreshParam(1);
|
||||
}
|
||||
else {
|
||||
heartBeatCounter_ ++;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// new rx frame recived!
|
||||
void ecmcCANOpenDevice::newRxFrame(can_frame *frame) {
|
||||
|
||||
// only validate if not master
|
||||
if (!validateFrame(frame) && !isMaster_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// forward to pdos
|
||||
for(int i=0 ; i<pdoCounter_; i++) {
|
||||
if(pdos_[i]) {
|
||||
pdos_[i]->newRxFrame(frame);
|
||||
}
|
||||
}
|
||||
|
||||
// forward to sdos
|
||||
for(int i=0 ; i<sdoCounter_; i++) {
|
||||
if(sdos_[i]) {
|
||||
sdos_[i]->newRxFrame(frame);
|
||||
}
|
||||
}
|
||||
|
||||
// NMT
|
||||
if(!isMaster_ && nmtActParam_) {
|
||||
checkNMT(frame);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// r 0x183 [8] 0x00 0x00 0x00 0x00 0x0B 0x40 0x04 0x20
|
||||
int ecmcCANOpenDevice::validateFrame(can_frame *frame) {
|
||||
|
||||
// nodeid is always lower 7bits.. Need to check this calc.. byte order?!
|
||||
uint8_t tempNodeId = frame->can_id & 0x7F;
|
||||
|
||||
if(tempNodeId != nodeId_) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int ecmcCANOpenDevice::checkNMT(can_frame *frame) {
|
||||
|
||||
// check if NMT frame
|
||||
if(frame->can_id == (ECMC_CANOPEN_NMT_BASE + nodeId_)) {
|
||||
if(frame->can_dlc == 1){
|
||||
switch(frame->data[0]) {
|
||||
case ECMC_CANOPEN_NMT_BOOT:
|
||||
nmtState_ = NMT_BOOT_UP;
|
||||
break;
|
||||
case ECMC_CANOPEN_NMT_STOP:
|
||||
nmtState_ = NMT_STOPPED;
|
||||
break;
|
||||
case ECMC_CANOPEN_NMT_OP:
|
||||
nmtState_ = NMT_OP;
|
||||
break;
|
||||
case ECMC_CANOPEN_NMT_PREOP:
|
||||
nmtState_ = NMT_BOOT_UP;
|
||||
break;
|
||||
default:
|
||||
nmtState_ = NMT_NOT_VALID;
|
||||
break;
|
||||
}
|
||||
}
|
||||
heartBeatCounter_ = 0;
|
||||
if(nmtState_ != nmtStateOld_) {
|
||||
nmtActParam_->refreshParam(1);
|
||||
}
|
||||
nmtStateOld_ = nmtState_;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ecmcCANOpenDevice::addPDO(uint32_t cobId,
|
||||
ecmc_can_direction rw,
|
||||
uint32_t ODSize,
|
||||
int readTimeoutMs,
|
||||
int writeCycleMs, //if <0 then write on demand..
|
||||
const char* name) {
|
||||
|
||||
if(pdoCounter_>= ECMC_CAN_DEVICE_PDO_MAX_COUNT) {
|
||||
return ECMC_CAN_PDO_INDEX_OUT_OF_RANGE;
|
||||
}
|
||||
|
||||
pdos_[pdoCounter_] = new ecmcCANOpenPDO(writeBuffer_,
|
||||
nodeId_,
|
||||
cobId,
|
||||
rw,
|
||||
ODSize,
|
||||
readTimeoutMs,
|
||||
writeCycleMs,
|
||||
exeSampleTimeMs_,
|
||||
name,
|
||||
dbgMode_);
|
||||
pdoCounter_++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ecmcCANOpenDevice::addSDO(uint32_t cobIdTx, // 0x580 + CobId
|
||||
uint32_t cobIdRx, // 0x600 + Cobid
|
||||
ecmc_can_direction rw,
|
||||
uint16_t ODIndex, // Object dictionary index
|
||||
uint8_t ODSubIndex, // Object dictionary subindex
|
||||
uint32_t ODSize,
|
||||
int readSampleTimeMs,
|
||||
const char* name) {
|
||||
|
||||
if(sdoCounter_>= ECMC_CAN_DEVICE_SDO_MAX_COUNT) {
|
||||
return ECMC_CAN_SDO_INDEX_OUT_OF_RANGE;
|
||||
}
|
||||
|
||||
sdos_[sdoCounter_] = new ecmcCANOpenSDO(writeBuffer_,
|
||||
nodeId_,
|
||||
cobIdTx,
|
||||
cobIdRx,
|
||||
rw,
|
||||
ODIndex,
|
||||
ODSubIndex,
|
||||
ODSize,
|
||||
readSampleTimeMs,
|
||||
exeSampleTimeMs_,
|
||||
name,
|
||||
&sdo1Lock_,
|
||||
sdoCounter_,
|
||||
dbgMode_);
|
||||
sdoCounter_++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t ecmcCANOpenDevice::getNodeId() {
|
||||
return nodeId_;
|
||||
}
|
||||
|
||||
void ecmcCANOpenDevice::initAsyn() {
|
||||
|
||||
ecmcAsynPortDriver *ecmcAsynPort = (ecmcAsynPortDriver *)getEcmcAsynPortDriver();
|
||||
if(!ecmcAsynPort) {
|
||||
printf("ERROR: ecmcAsynPort NULL.");
|
||||
throw std::runtime_error( "ERROR: ecmcAsynPort NULL." );
|
||||
}
|
||||
|
||||
// Add resultdata "plugin.can.dev%d.<name>"
|
||||
std::string paramName = ECMC_PLUGIN_ASYN_PREFIX + std::string(".dev") +
|
||||
to_string(nodeId_) + ".nmtstate";
|
||||
|
||||
nmtActParam_ = ecmcAsynPort->addNewAvailParam(
|
||||
paramName.c_str(), // name
|
||||
asynParamInt32, // asyn type
|
||||
(uint8_t*)&nmtState_, // pointer to data
|
||||
sizeof(nmtState_), // size of data
|
||||
ECMC_EC_S32, // ecmc data type
|
||||
0); // die if fail
|
||||
|
||||
if(!nmtActParam_) {
|
||||
printf("ERROR: Failed create asyn param for NMT state.");
|
||||
throw std::runtime_error( "ERROR: Failed create asyn param for data: " + paramName);
|
||||
}
|
||||
|
||||
nmtActParam_->addSupportedAsynType(asynParamUInt32Digital);
|
||||
|
||||
nmtActParam_->refreshParam(1); // read once into asyn param lib
|
||||
ecmcAsynPort->callParamCallbacks(ECMC_ASYN_DEFAULT_LIST, ECMC_ASYN_DEFAULT_ADDR);
|
||||
}
|
||||
// Avoid issues with std:to_string()
|
||||
std::string ecmcCANOpenDevice::to_string(int value) {
|
||||
std::ostringstream os;
|
||||
os << value;
|
||||
return os.str();
|
||||
}
|
||||
95
src/ecmcCANOpenDevice.h
Normal file
95
src/ecmcCANOpenDevice.h
Normal file
@@ -0,0 +1,95 @@
|
||||
/*************************************************************************\
|
||||
* 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.
|
||||
*
|
||||
* ecmcCANOpenDevice.h
|
||||
*
|
||||
* Created on: Mar 08, 2021
|
||||
* Author: anderssandstrom
|
||||
*
|
||||
\*************************************************************************/
|
||||
#ifndef ECMC_CANOPEN_DEVICE_H_
|
||||
#define ECMC_CANOPEN_DEVICE_H_
|
||||
|
||||
#include <stdexcept>
|
||||
#include <atomic>
|
||||
#include "ecmcDataItem.h"
|
||||
#include "ecmcAsynPortDriver.h"
|
||||
#include "ecmcSocketCANDefs.h"
|
||||
#include "ecmcCANOpenPDO.h"
|
||||
#include "ecmcCANOpenSDO.h"
|
||||
#include "inttypes.h"
|
||||
#include <string>
|
||||
#include "ecmcSocketCANWriteBuffer.h"
|
||||
#include "epicsMutex.h"
|
||||
|
||||
|
||||
#include <linux/can.h>
|
||||
#include <linux/can/raw.h>
|
||||
|
||||
#define ECMC_CAN_DEVICE_PDO_MAX_COUNT 8
|
||||
#define ECMC_CAN_DEVICE_SDO_MAX_COUNT 8
|
||||
#define ECMC_CAN_ERROR_PDO_TIMEOUT 100
|
||||
|
||||
#define ECMC_CAN_PDO_INDEX_OUT_OF_RANGE 1000
|
||||
#define ECMC_CAN_SDO_INDEX_OUT_OF_RANGE 1001
|
||||
|
||||
class ecmcCANOpenDevice {
|
||||
public:
|
||||
ecmcCANOpenDevice(ecmcSocketCANWriteBuffer* writeBuffer,
|
||||
uint32_t nodeId,
|
||||
int exeSampleTimeMs,
|
||||
const char* name,
|
||||
int heartTimeoutMs,
|
||||
int dbgMode);
|
||||
virtual ~ecmcCANOpenDevice();
|
||||
void execute();
|
||||
void newRxFrame(can_frame *frame);
|
||||
int addPDO(uint32_t cobId,
|
||||
ecmc_can_direction rw,
|
||||
uint32_t ODSize,
|
||||
int readTimeoutMs,
|
||||
int writeCycleMs, //if <0 then write on demand.
|
||||
const char* name);
|
||||
|
||||
int addSDO(uint32_t cobIdTx, // 0x580 + CobId
|
||||
uint32_t cobIdRx, // 0x600 + Cobid
|
||||
ecmc_can_direction rw,
|
||||
uint16_t ODIndex, // Object dictionary index
|
||||
uint8_t ODSubIndex, // Object dictionary subindex
|
||||
uint32_t ODSize,
|
||||
int readSampleTimeMs,
|
||||
const char* name);
|
||||
uint32_t getNodeId();
|
||||
protected:
|
||||
int validateFrame(can_frame *frame);
|
||||
int checkNMT(can_frame *frame);
|
||||
|
||||
ecmcSocketCANWriteBuffer *writeBuffer_;
|
||||
uint32_t nodeId_; // with cobid
|
||||
int exeSampleTimeMs_;
|
||||
int exeCounter_;
|
||||
int errorCode_;
|
||||
int dbgMode_;
|
||||
int pdoCounter_;
|
||||
int sdoCounter_;
|
||||
char* name_;
|
||||
int heartTimeoutMs_;
|
||||
int heartBeatCounter_;
|
||||
ecmcCANOpenPDO *pdos_[ECMC_CAN_DEVICE_PDO_MAX_COUNT];
|
||||
ecmcCANOpenSDO *sdos_[ECMC_CAN_DEVICE_SDO_MAX_COUNT];
|
||||
bool isMaster_;
|
||||
std::atomic_flag sdo1Lock_;
|
||||
ecmc_nmt_state_act nmtState_;
|
||||
ecmc_nmt_state_act nmtStateOld_;
|
||||
|
||||
//ASYN
|
||||
void initAsyn();
|
||||
ecmcAsynDataItem *nmtActParam_;
|
||||
|
||||
std::string to_string(int value);
|
||||
};
|
||||
|
||||
#endif /* ECMC_CANOPEN_DEVICE_H_ */
|
||||
|
||||
99
src/ecmcCANOpenMaster.cpp
Normal file
99
src/ecmcCANOpenMaster.cpp
Normal file
@@ -0,0 +1,99 @@
|
||||
/*************************************************************************\
|
||||
* 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.
|
||||
*
|
||||
* ecmcCANOpenMaster.cpp
|
||||
*
|
||||
* Created on: Mar 09, 2021
|
||||
* Author: anderssandstrom
|
||||
*
|
||||
\*************************************************************************/
|
||||
|
||||
// Needed to get headers in ecmc right...
|
||||
#define ECMC_IS_PLUGIN
|
||||
|
||||
#include <sstream>
|
||||
#include "ecmcCANOpenMaster.h"
|
||||
|
||||
/**
|
||||
* ecmc ecmcCANOpenMaster class
|
||||
*/
|
||||
ecmcCANOpenMaster::ecmcCANOpenMaster(ecmcSocketCANWriteBuffer* writeBuffer,
|
||||
uint32_t nodeId,
|
||||
int exeSampleTimeMs,
|
||||
int lssSampleTimeMs,
|
||||
int syncSampleTimeMs,
|
||||
int heartSampleTimeMs,
|
||||
const char* name,
|
||||
int dbgMode):
|
||||
ecmcCANOpenDevice(writeBuffer,
|
||||
nodeId, // 0x580 + CobId
|
||||
exeSampleTimeMs,
|
||||
name,
|
||||
0, //NMT timeout is 0 for master (always OP)
|
||||
dbgMode) {
|
||||
lssPdo_ = NULL;
|
||||
syncPdo_ = NULL;
|
||||
heartPdo_ = NULL;
|
||||
isMaster_ = true;
|
||||
lssSampleTimeMs_ = lssSampleTimeMs;
|
||||
syncSampleTimeMs_ = syncSampleTimeMs;
|
||||
heartSampleTimeMs_ = heartSampleTimeMs;
|
||||
|
||||
// Master is always in OP
|
||||
if(nmtActParam_) {
|
||||
nmtState_ = NMT_OP;
|
||||
nmtStateOld_ = NMT_OP;
|
||||
nmtActParam_->refreshParam(1);
|
||||
}
|
||||
|
||||
int errorCode = 0;
|
||||
|
||||
// lssPdo_ = new ecmcCANOpenPDO( writeBuffer_, 0x7E5,DIR_WRITE,0,0,1000,exeSampleTimeMs_,"lss", cfgDbgMode_);
|
||||
errorCode = addPDO(0x7E5, // uint32_t cobId,
|
||||
DIR_WRITE, // ecmc_can_direction rw,
|
||||
0, // uint32_t ODSize,
|
||||
0, // int readTimeoutMs,
|
||||
lssSampleTimeMs, // int writeCycleMs, if < 0 then write on demand.
|
||||
"lss"); // const char* name);
|
||||
if(errorCode) {
|
||||
throw std::runtime_error( "LSS PDO NULL.");
|
||||
}
|
||||
lssPdo_ = pdos_[pdoCounter_-1];
|
||||
|
||||
// Test sync signal
|
||||
// can0 0x80 [0]
|
||||
// syncPdo_ = new ecmcCANOpenPDO( writeBuffer_, 0x80,DIR_WRITE,0,0,1000,exeSampleTimeMs_,"sync", cfgDbgMode_);
|
||||
errorCode = addPDO(0x80, // uint32_t cobId,
|
||||
DIR_WRITE, // ecmc_can_direction rw,
|
||||
0, // uint32_t ODSize,
|
||||
0, // int readTimeoutMs,
|
||||
syncSampleTimeMs, // int writeCycleMs, if < 0 then write on demand.
|
||||
"sync"); // const char* name);
|
||||
|
||||
if(errorCode) {
|
||||
throw std::runtime_error( "Sync PDO NULL.");
|
||||
}
|
||||
syncPdo_ = pdos_[pdoCounter_-1];
|
||||
|
||||
// Test heartbeat signal
|
||||
// can0 0x701 [1] 05
|
||||
//can_add_write(1793,1,5,0,0,0,0,0,0,0);
|
||||
//heartPdo_ = new ecmcCANOpenPDO( writeBuffer_, 0x701,DIR_WRITE,1,0,1000,exeSampleTimeMs_,"heartbeat",cfgDbgMode_);
|
||||
//heartPdo_->setValue(5);
|
||||
errorCode = addPDO(0x700+nodeId_, // uint32_t cobId,
|
||||
DIR_WRITE, // ecmc_can_direction rw,
|
||||
1, // uint32_t ODSize,
|
||||
0, // int readTimeoutMs,
|
||||
heartSampleTimeMs, // int writeCycleMs, if < 0 then write on demand.
|
||||
"heart"); // const char* name);
|
||||
if(errorCode) {
|
||||
throw std::runtime_error( "Heart PDO NULL.");
|
||||
}
|
||||
heartPdo_ = pdos_[pdoCounter_-1];
|
||||
heartPdo_->setValue(5);
|
||||
}
|
||||
|
||||
ecmcCANOpenMaster::~ecmcCANOpenMaster() {
|
||||
}
|
||||
52
src/ecmcCANOpenMaster.h
Normal file
52
src/ecmcCANOpenMaster.h
Normal file
@@ -0,0 +1,52 @@
|
||||
/*************************************************************************\
|
||||
* 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.
|
||||
*
|
||||
* ecmcCANOpenMaster.h
|
||||
*
|
||||
* Created on: Mar 08, 2021
|
||||
* Author: anderssandstrom
|
||||
*
|
||||
\*************************************************************************/
|
||||
#ifndef ECMC_CANOPEN_MASTER_H_
|
||||
#define ECMC_CANOPEN_MASTER_H_
|
||||
|
||||
#include <stdexcept>
|
||||
#include "ecmcDataItem.h"
|
||||
#include "ecmcAsynPortDriver.h"
|
||||
#include "ecmcSocketCANDefs.h"
|
||||
#include "ecmcCANOpenPDO.h"
|
||||
#include "ecmcCANOpenSDO.h"
|
||||
#include "ecmcCANOpenDevice.h"
|
||||
#include "inttypes.h"
|
||||
#include <string>
|
||||
#include "ecmcSocketCANWriteBuffer.h"
|
||||
#include "epicsMutex.h"
|
||||
|
||||
#include <linux/can.h>
|
||||
#include <linux/can/raw.h>
|
||||
|
||||
class ecmcCANOpenMaster : public ecmcCANOpenDevice {
|
||||
public:
|
||||
ecmcCANOpenMaster(ecmcSocketCANWriteBuffer* writeBuffer,
|
||||
uint32_t nodeId,
|
||||
int exeSampleTimeMs,
|
||||
int lssSampleTimeMs,
|
||||
int syncSampleTimeMs,
|
||||
int heartSampleTimeMs,
|
||||
const char* name,
|
||||
int dbgMode);
|
||||
~ecmcCANOpenMaster();
|
||||
|
||||
private:
|
||||
ecmcCANOpenPDO *lssPdo_;
|
||||
ecmcCANOpenPDO *syncPdo_;
|
||||
ecmcCANOpenPDO *heartPdo_;
|
||||
int lssSampleTimeMs_;
|
||||
int syncSampleTimeMs_;
|
||||
int heartSampleTimeMs_;
|
||||
};
|
||||
|
||||
#endif /* ECMC_CANOPEN_MASTER_H_ */
|
||||
|
||||
268
src/ecmcCANOpenPDO.cpp
Normal file
268
src/ecmcCANOpenPDO.cpp
Normal file
@@ -0,0 +1,268 @@
|
||||
/*************************************************************************\
|
||||
* 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.
|
||||
*
|
||||
* ecmcCANOpenPDO.cpp
|
||||
*
|
||||
* Created on: Mar 22, 2020
|
||||
* Author: anderssandstrom
|
||||
*
|
||||
\*************************************************************************/
|
||||
|
||||
// Needed to get headers in ecmc right...
|
||||
#define ECMC_IS_PLUGIN
|
||||
|
||||
#include <sstream>
|
||||
#include "ecmcCANOpenPDO.h"
|
||||
#include "ecmcAsynPortDriver.h"
|
||||
#include "ecmcPluginClient.h"
|
||||
|
||||
// Calback function to init write from asyn
|
||||
asynStatus asynWritePDOValue(void* data, size_t bytes, asynParamType asynParType,void *userObj) {
|
||||
// userobj = NULL
|
||||
if(!userObj) {
|
||||
printf("Error: asynWritePDOValue() fail, no user obj defined.\n");
|
||||
return asynError;
|
||||
}
|
||||
ecmcCANOpenPDO* pdo = (ecmcCANOpenPDO*)userObj;
|
||||
int bytesToCp = bytes;
|
||||
if (bytes > 8) {
|
||||
bytesToCp = 8;
|
||||
}
|
||||
uint64_t tempData = 0;
|
||||
|
||||
memcpy(&tempData,data,bytesToCp);
|
||||
pdo->setValue(tempData);
|
||||
pdo->writeValue();
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
/**
|
||||
* ecmc ecmcCANOpenPDO class
|
||||
*/
|
||||
ecmcCANOpenPDO::ecmcCANOpenPDO(ecmcSocketCANWriteBuffer* writeBuffer,
|
||||
uint32_t nodeId,
|
||||
uint32_t cobId, // 0x580 + CobId
|
||||
ecmc_can_direction rw,
|
||||
uint32_t ODSize,
|
||||
int readTimeoutMs,
|
||||
int writeCycleMs,
|
||||
int exeSampleTimeMs,
|
||||
const char* name,
|
||||
int dbgMode) {
|
||||
|
||||
writeBuffer_ = writeBuffer;
|
||||
nodeId_ = nodeId;
|
||||
cobId_ = cobId;
|
||||
ODSize_ = ODSize;
|
||||
name_ = strdup(name);
|
||||
|
||||
if(ODSize_ > 8) {
|
||||
ODSize_ = 8;
|
||||
}
|
||||
|
||||
readTimeoutMs_ = readTimeoutMs;
|
||||
writeCycleMs_ = writeCycleMs;
|
||||
exeSampleTimeMs_ = exeSampleTimeMs;
|
||||
rw_ = rw;
|
||||
exeCounter_ = 0;
|
||||
busy_ = 0;
|
||||
errorCode_ = 0;
|
||||
dataBuffer_ = new uint8_t[ODSize_];
|
||||
memset(dataBuffer_,0,ODSize_);
|
||||
dbgMode_ = dbgMode;
|
||||
refreshNeeded_ = 0;
|
||||
|
||||
writeFrame_.can_id = cobId_;
|
||||
writeFrame_.can_dlc = ODSize; // data length
|
||||
writeFrame_.data[0] = 0; // request read cmd
|
||||
writeFrame_.data[1] = 0;
|
||||
writeFrame_.data[2] = 0;
|
||||
writeFrame_.data[3] = 0;
|
||||
writeFrame_.data[4] = 0;
|
||||
writeFrame_.data[5] = 0;
|
||||
writeFrame_.data[6] = 0;
|
||||
writeFrame_.data[7] = 0;
|
||||
|
||||
dataMutex_ = epicsMutexCreate();
|
||||
initAsyn();
|
||||
}
|
||||
|
||||
ecmcCANOpenPDO::~ecmcCANOpenPDO() {
|
||||
delete[] dataBuffer_;
|
||||
free(name_);
|
||||
}
|
||||
|
||||
void ecmcCANOpenPDO::execute() {
|
||||
|
||||
exeCounter_++;
|
||||
|
||||
if(rw_ == DIR_READ) {
|
||||
if(exeCounter_* exeSampleTimeMs_ >= readTimeoutMs_) {
|
||||
errorCode_ = ECMC_CAN_ERROR_PDO_TIMEOUT;
|
||||
if(dbgMode_) {
|
||||
printf("ECMC_CAN_ERROR_PDO_TIMEOUT (0x%x)\n",errorCode_);
|
||||
}
|
||||
refreshNeeded_ = 1;
|
||||
exeCounter_ = 0;
|
||||
}
|
||||
}
|
||||
else { //DIR_WRITE
|
||||
if(writeCycleMs_<=0) { // Only write on demand if cycle is less than 0
|
||||
exeCounter_ = 0;
|
||||
return;
|
||||
}
|
||||
if(exeCounter_* exeSampleTimeMs_ >= writeCycleMs_) {
|
||||
writeValue(); // write in defined cycle
|
||||
exeCounter_ = 0;
|
||||
}
|
||||
}
|
||||
// Refresh in sync with ecmc
|
||||
refreshAsynParams();
|
||||
}
|
||||
|
||||
// new rx frame recived!
|
||||
void ecmcCANOpenPDO::newRxFrame(can_frame *frame) {
|
||||
// Wait for:
|
||||
if(rw_ == DIR_READ) {
|
||||
if(validateFrame(frame)) {
|
||||
epicsMutexLock(dataMutex_);
|
||||
memset(dataBuffer_,0,ODSize_);
|
||||
memcpy(dataBuffer_, &(frame->data[0]),frame->can_dlc);
|
||||
epicsMutexUnlock(dataMutex_);
|
||||
refreshNeeded_ = 1;
|
||||
errorCode_ = 0;
|
||||
refreshNeeded_ = 1;
|
||||
if(dbgMode_) {
|
||||
printBuffer();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ecmcCANOpenPDO::printBuffer() {
|
||||
if(!dataBuffer_) {
|
||||
return;
|
||||
}
|
||||
|
||||
for(uint32_t i = 0; i < ODSize_; i = i + 2) {
|
||||
uint16_t test;
|
||||
memcpy(&test,&dataBuffer_[i],2);
|
||||
printf("data[%02d]: %u\n",i/2,test);
|
||||
}
|
||||
}
|
||||
|
||||
// r 0x183 [8] 0x00 0x00 0x00 0x00 0x0B 0x40 0x04 0x20
|
||||
int ecmcCANOpenPDO::validateFrame(can_frame *frame) {
|
||||
if(frame->can_id != cobId_) {
|
||||
return 0;
|
||||
}
|
||||
if(frame->can_dlc != ODSize_) {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void ecmcCANOpenPDO::setValue(uint64_t data) {
|
||||
epicsMutexLock(dataMutex_);
|
||||
memcpy(dataBuffer_, &data, ODSize_);
|
||||
epicsMutexUnlock(dataMutex_);
|
||||
}
|
||||
|
||||
int ecmcCANOpenPDO::writeValue() {
|
||||
if(writeFrame_.can_dlc > 0) {
|
||||
epicsMutexLock(dataMutex_);
|
||||
memcpy(&(writeFrame_.data[0]), dataBuffer_ ,writeFrame_.can_dlc);
|
||||
epicsMutexUnlock(dataMutex_);
|
||||
}
|
||||
int errorCode = writeBuffer_->addWriteCAN(&writeFrame_);
|
||||
refreshNeeded_ = 1;
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
void ecmcCANOpenPDO::initAsyn() {
|
||||
|
||||
ecmcAsynPortDriver *ecmcAsynPort = (ecmcAsynPortDriver *)getEcmcAsynPortDriver();
|
||||
if(!ecmcAsynPort) {
|
||||
printf("ERROR: ecmcAsynPort NULL.");
|
||||
throw std::runtime_error( "ERROR: ecmcAsynPort NULL." );
|
||||
}
|
||||
|
||||
// Add resultdata "plugin.can.dev%d.<name>"
|
||||
std::string paramName = ECMC_PLUGIN_ASYN_PREFIX + std::string(".dev") +
|
||||
to_string(nodeId_) + ".pdo" /*+ to_string(objIndex_) */
|
||||
+ "." + std::string(name_);
|
||||
|
||||
dataParam_ = ecmcAsynPort->addNewAvailParam(
|
||||
paramName.c_str(), // name
|
||||
asynParamInt8Array, // asyn type
|
||||
dataBuffer_, // pointer to data
|
||||
ODSize_, // size of data
|
||||
ECMC_EC_U8, // ecmc data type
|
||||
0); // die if fail
|
||||
|
||||
if(!dataParam_) {
|
||||
printf("ERROR: Failed create asyn param for data.");
|
||||
throw std::runtime_error( "ERROR: Failed create asyn param for data: " + paramName);
|
||||
}
|
||||
|
||||
// Allow different types depending on size
|
||||
if(ODSize_>1){
|
||||
dataParam_->addSupportedAsynType(asynParamInt16Array);
|
||||
}
|
||||
if(ODSize_>3){
|
||||
dataParam_->addSupportedAsynType(asynParamInt32Array);
|
||||
dataParam_->addSupportedAsynType(asynParamFloat32Array);
|
||||
dataParam_->addSupportedAsynType(asynParamInt32);
|
||||
}
|
||||
if(ODSize_>7){
|
||||
dataParam_->addSupportedAsynType(asynParamFloat64Array);
|
||||
dataParam_->addSupportedAsynType(asynParamFloat64);
|
||||
}
|
||||
|
||||
dataParam_->setAllowWriteToEcmc(rw_ == DIR_WRITE);
|
||||
|
||||
if(rw_ == DIR_WRITE) {
|
||||
dataParam_->setExeCmdFunctPtr(asynWritePDOValue,this);
|
||||
}
|
||||
|
||||
dataParam_->refreshParam(1); // read once into asyn param lib
|
||||
ecmcAsynPort->callParamCallbacks(ECMC_ASYN_DEFAULT_LIST, ECMC_ASYN_DEFAULT_ADDR);
|
||||
|
||||
// Add resultdata "plugin.can.dev%d.error"
|
||||
paramName = ECMC_PLUGIN_ASYN_PREFIX + std::string(".dev") +
|
||||
to_string(nodeId_) + ".pdo" + "." + std::string(name_) + std::string(".error");
|
||||
|
||||
errorParam_ = ecmcAsynPort->addNewAvailParam(
|
||||
paramName.c_str(), // name
|
||||
asynParamInt32, // asyn type
|
||||
(uint8_t*)&errorCode_, // pointer to data
|
||||
sizeof(errorCode_), // size of data
|
||||
ECMC_EC_U32, // ecmc data type
|
||||
0); // die if fail
|
||||
|
||||
if(!errorParam_) {
|
||||
printf("ERROR: Failed create asyn param for data.");
|
||||
throw std::runtime_error( "ERROR: Failed create asyn param for data: " + paramName);
|
||||
}
|
||||
|
||||
errorParam_->setAllowWriteToEcmc(false); // need to callback here
|
||||
errorParam_->refreshParam(1); // read once into asyn param lib
|
||||
ecmcAsynPort->callParamCallbacks(ECMC_ASYN_DEFAULT_LIST, ECMC_ASYN_DEFAULT_ADDR);
|
||||
}
|
||||
|
||||
void ecmcCANOpenPDO::refreshAsynParams() {
|
||||
if(refreshNeeded_) {
|
||||
dataParam_->refreshParamRT(1); // read once into asyn param lib
|
||||
errorParam_->refreshParamRT(1); // read once into asyn param lib
|
||||
}
|
||||
refreshNeeded_ = 0;
|
||||
}
|
||||
|
||||
// Avoid issues with std:to_string()
|
||||
std::string ecmcCANOpenPDO::to_string(int value) {
|
||||
std::ostringstream os;
|
||||
os << value;
|
||||
return os.str();
|
||||
}
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "inttypes.h"
|
||||
#include <string>
|
||||
#include "ecmcSocketCANWriteBuffer.h"
|
||||
#include "epicsMutex.h"
|
||||
|
||||
#include <linux/can.h>
|
||||
#include <linux/can/raw.h>
|
||||
@@ -29,12 +30,14 @@
|
||||
class ecmcCANOpenPDO {
|
||||
public:
|
||||
ecmcCANOpenPDO(ecmcSocketCANWriteBuffer* writeBuffer,
|
||||
uint32_t nodeId,
|
||||
uint32_t cobId,
|
||||
ecmc_can_direction rw,
|
||||
uint32_t ODSize,
|
||||
int readTimeoutMs,
|
||||
int writeCycleMs, //if <0 the write on demand..
|
||||
int exeSampleTimeMs,
|
||||
const char *name,
|
||||
int dbgMode);
|
||||
~ecmcCANOpenPDO();
|
||||
void execute();
|
||||
@@ -46,6 +49,7 @@ class ecmcCANOpenPDO {
|
||||
int validateFrame(can_frame *frame);
|
||||
ecmcSocketCANWriteBuffer *writeBuffer_;
|
||||
uint32_t cobId_; // with cobid
|
||||
uint32_t nodeId_;
|
||||
int readTimeoutMs_;
|
||||
int writeCycleMs_;
|
||||
int exeSampleTimeMs_;
|
||||
@@ -58,6 +62,17 @@ class ecmcCANOpenPDO {
|
||||
void printBuffer();
|
||||
int dbgMode_;
|
||||
can_frame writeFrame_;
|
||||
epicsMutexId dataMutex_;
|
||||
char* name_;
|
||||
|
||||
static std::string to_string(int value);
|
||||
|
||||
//ASYN
|
||||
void initAsyn();
|
||||
void refreshAsynParams();
|
||||
int refreshNeeded_;
|
||||
ecmcAsynDataItem *dataParam_;
|
||||
ecmcAsynDataItem *errorParam_;
|
||||
};
|
||||
|
||||
#endif /* ECMC_CANOPEN_PDO_H_ */
|
||||
@@ -7,7 +7,6 @@
|
||||
*
|
||||
* Created on: Mar 22, 2020
|
||||
* Author: anderssandstrom
|
||||
* Credits to https://github.com/sgreg/dynamic-loading
|
||||
*
|
||||
\*************************************************************************/
|
||||
|
||||
@@ -16,13 +15,30 @@
|
||||
|
||||
#include <sstream>
|
||||
#include "ecmcCANOpenSDO.h"
|
||||
#include "ecmcAsynPortDriver.h"
|
||||
#include "ecmcPluginClient.h"
|
||||
|
||||
|
||||
#define ECMC_SDO_TRANSFER_MAX_BYTES 7
|
||||
|
||||
// Calback function to init write from asyn
|
||||
asynStatus asynWriteSDOValue(void* data, size_t bytes, asynParamType asynParType,void *userObj) {
|
||||
// userobj = NULL
|
||||
if(!userObj) {
|
||||
printf("Error: asynWriteSDOValue() fail, no user obj defined.\n");
|
||||
return asynError;
|
||||
}
|
||||
ecmcCANOpenSDO* sdo = (ecmcCANOpenSDO*)userObj;
|
||||
|
||||
sdo->setValue((uint8_t*)data,bytes);
|
||||
sdo->writeValue();
|
||||
return asynSuccess;
|
||||
}
|
||||
/**
|
||||
* ecmc ecmcCANOpenSDO class
|
||||
*/
|
||||
ecmcCANOpenSDO::ecmcCANOpenSDO(ecmcSocketCANWriteBuffer* writeBuffer,
|
||||
uint32_t nodeId,
|
||||
uint32_t cobIdTx, // 0x580 + CobId
|
||||
uint32_t cobIdRx, // 0x600 + Cobid
|
||||
ecmc_can_direction rw,
|
||||
@@ -31,15 +47,29 @@ ecmcCANOpenSDO::ecmcCANOpenSDO(ecmcSocketCANWriteBuffer* writeBuffer,
|
||||
uint32_t ODSize,
|
||||
int readSampleTimeMs,
|
||||
int exeSampleTimeMs,
|
||||
const char *name,
|
||||
std::atomic_flag *ptrSdo1Lock,
|
||||
int objIndex,
|
||||
int dbgMode) {
|
||||
|
||||
writeBuffer_ = writeBuffer;
|
||||
nodeId_ = nodeId;
|
||||
cobIdRx_ = cobIdRx;
|
||||
cobIdTx_ = cobIdTx;
|
||||
ODIndex_ = ODIndex;
|
||||
ODSubIndex_ = ODSubIndex;
|
||||
ODSize_ = ODSize;
|
||||
objIndex_ = objIndex;
|
||||
dbgMode_ = dbgMode;
|
||||
name_ = strdup(name);
|
||||
errorCode_ = 0;
|
||||
refreshNeeded_ = 0;
|
||||
ptrSdo1Lock_ = ptrSdo1Lock;
|
||||
dataMutex_ = epicsMutexCreate();
|
||||
getLockMutex_ = epicsMutexCreate();
|
||||
errorParam_ = NULL;
|
||||
dataParam_ = NULL;
|
||||
writePending_ = NULL;
|
||||
// convert to ODIndex_ to indiviual bytes struct
|
||||
memcpy(&ODIndexBytes_, &ODIndex, 2);
|
||||
memcpy(&ODLengthBytes_, &ODSize_, 4);
|
||||
@@ -48,14 +78,18 @@ ecmcCANOpenSDO::ecmcCANOpenSDO(ecmcSocketCANWriteBuffer* writeBuffer,
|
||||
exeSampleTimeMs_ = exeSampleTimeMs;
|
||||
rw_ = rw;
|
||||
exeCounter_ = 0;
|
||||
busy_ = 0;
|
||||
recivedBytes_ = 0;
|
||||
writtenBytes_ = 0;
|
||||
readStates_ = READ_IDLE;
|
||||
writeStates_ = WRITE_IDLE;
|
||||
useTg1Frame_ = 1;
|
||||
dataBuffer_ = new uint8_t(ODSize_);
|
||||
dataBuffer_ = new uint8_t[ODSize_];
|
||||
tempDataBuffer_ = new uint8_t[ODSize_];
|
||||
|
||||
memset(dataBuffer_,0,ODSize_);
|
||||
memset(tempDataBuffer_,0,ODSize_);
|
||||
|
||||
busyCounter_ = 0;
|
||||
// Request data (send on slave RX)
|
||||
// w 0x603 [8] 0x40 0x40 0x26 0x00 0x00 0x00 0x00 0x00
|
||||
readReqTransferFrame_.can_id = cobIdRx;
|
||||
@@ -165,36 +199,70 @@ ecmcCANOpenSDO::ecmcCANOpenSDO(ecmcSocketCANWriteBuffer* writeBuffer,
|
||||
writeConfReqFrameTg1_.data[4] = 0;
|
||||
writeConfReqFrameTg1_.data[5] = 0;
|
||||
writeConfReqFrameTg1_.data[6] = 0;
|
||||
writeConfReqFrameTg1_.data[7] = 0;
|
||||
writeConfReqFrameTg1_.data[7] = 0;
|
||||
busy_ = false;
|
||||
|
||||
initAsyn();
|
||||
}
|
||||
|
||||
ecmcCANOpenSDO::~ecmcCANOpenSDO() {
|
||||
delete[] dataBuffer_;
|
||||
delete[] tempDataBuffer_;
|
||||
free(name_);
|
||||
}
|
||||
|
||||
void ecmcCANOpenSDO::execute() {
|
||||
|
||||
if(busy_) {
|
||||
busyCounter_++;
|
||||
} else {
|
||||
busyCounter_ = 0;
|
||||
}
|
||||
|
||||
if(busyCounter_>ECMC_SDO_REPLY_TIMOUT_MS) {
|
||||
// cancel read or write
|
||||
printf("Error: SDO BUSY timeout!! %s\n",name_);
|
||||
memset(tempDataBuffer_,0,ODSize_);
|
||||
readStates_ = READ_IDLE;
|
||||
writeStates_ = WRITE_IDLE;
|
||||
exeCounter_ = 0;
|
||||
busyCounter_ = 0;
|
||||
errorCode_ = ECMC_CAN_ERROR_SDO_TIMEOUT;
|
||||
errorParam_->refreshParamRT(1);
|
||||
unlockSdo1();
|
||||
}
|
||||
if(!busy_ && writePending_) {
|
||||
// Try to write pending value in tempDataBuffer_
|
||||
writeValue();
|
||||
}
|
||||
|
||||
exeCounter_++;
|
||||
if(exeCounter_* exeSampleTimeMs_ >= readSampleTimeMs_ && ! busy_) {
|
||||
exeCounter_ =0;
|
||||
if(rw_ == DIR_READ) {
|
||||
busy_ = 1;
|
||||
|
||||
if(exeCounter_* exeSampleTimeMs_ < readSampleTimeMs_ && rw_ == DIR_READ) { // do not risk overflow
|
||||
exeCounter_++;
|
||||
} else { // Counter is higher, try to write
|
||||
if(rw_ == DIR_READ) {
|
||||
|
||||
if(!tryLockSdo1()) {
|
||||
// wait for busy to go down
|
||||
return;
|
||||
}
|
||||
|
||||
exeCounter_ =0;
|
||||
readStates_ = READ_REQ_TRANSFER;
|
||||
if(dbgMode_) {
|
||||
printf("STATE = READ_REQ_TRANSFER\n");
|
||||
printf("STATE = READ_REQ_TRANSFER %s\n",name_);
|
||||
}
|
||||
// IMPORTANT!! LOCKLOCK!!!! LOCK all slave trafic while 0x583 and 0x603 for any other trafic while processing
|
||||
//initiate
|
||||
recivedBytes_ = 0;
|
||||
readStates_ = READ_WAIT_FOR_CONF;
|
||||
writeBuffer_->addWriteCAN(&readReqTransferFrame_);
|
||||
|
||||
if(dbgMode_) {
|
||||
printf("STATE = READ_WAIT_FOR_CONF\n");
|
||||
printf("STATE = READ_WAIT_FOR_CONF %s\n",name_);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Refresh in sync with ecmc
|
||||
refreshAsynParams();
|
||||
}
|
||||
|
||||
// new rx frame recived!
|
||||
@@ -202,7 +270,7 @@ void ecmcCANOpenSDO::newRxFrame(can_frame *frame) {
|
||||
// Wait for:
|
||||
// # r 0x583 [8] 0x41 0x40 0x26 0x00 0x38 0x00 0x00 0x00
|
||||
int errorCode = 0;
|
||||
if(!busy_) {
|
||||
if(!busy_) {
|
||||
// Not waiting for any data..
|
||||
return;
|
||||
}
|
||||
@@ -218,6 +286,10 @@ void ecmcCANOpenSDO::newRxFrame(can_frame *frame) {
|
||||
else { // Write
|
||||
errorCode = writeDataStateMachine(frame);
|
||||
}
|
||||
if(errorCode && (errorCode_ != errorCode)) {
|
||||
errorCode_ = errorCode;
|
||||
refreshNeeded_ = 1;
|
||||
}
|
||||
}
|
||||
|
||||
int ecmcCANOpenSDO::readDataStateMachine(can_frame *frame) {
|
||||
@@ -230,20 +302,20 @@ int ecmcCANOpenSDO::readDataStateMachine(can_frame *frame) {
|
||||
}
|
||||
readStates_ = READ_WAIT_FOR_DATA; //Next frame should be data!
|
||||
if(dbgMode_) {
|
||||
printf("STATE = READ_WAIT_FOR_DATA\n");
|
||||
printf("STATE = READ_WAIT_FOR_DATA %s\n",name_);
|
||||
}
|
||||
writeBuffer_->addWriteCAN(&readConfReqFrameTg0_); // Send tg0 frame and wait for data, also size must match to go ahead
|
||||
useTg1Frame_ = 1;
|
||||
break;
|
||||
case READ_WAIT_FOR_DATA:
|
||||
//Add data to buffer
|
||||
bytesToRead = frame->can_dlc-1;
|
||||
bytesToRead = frame->can_dlc - 1;
|
||||
if( bytesToRead > ECMC_SDO_TRANSFER_MAX_BYTES) {
|
||||
bytesToRead = ECMC_SDO_TRANSFER_MAX_BYTES;
|
||||
}
|
||||
|
||||
if(bytesToRead + recivedBytes_ <= ODSize_) {
|
||||
memcpy(dataBuffer_ + recivedBytes_, &(frame->data[1]),bytesToRead);
|
||||
memcpy(tempDataBuffer_ + recivedBytes_, &(frame->data[1]),bytesToRead);
|
||||
recivedBytes_ += bytesToRead;
|
||||
}
|
||||
if(recivedBytes_ < ODSize_) { // Ask for more data but must toggle so alternat the prepared frames
|
||||
@@ -260,14 +332,20 @@ int ecmcCANOpenSDO::readDataStateMachine(can_frame *frame) {
|
||||
}
|
||||
|
||||
if (recivedBytes_ >= ODSize_) {
|
||||
readStates_ =READ_IDLE;
|
||||
busy_ = 0;
|
||||
readStates_ =READ_IDLE;
|
||||
useTg1Frame_ = 0;
|
||||
|
||||
epicsMutexLock(dataMutex_);
|
||||
memcpy(dataBuffer_,tempDataBuffer_,ODSize_);
|
||||
epicsMutexUnlock(dataMutex_);
|
||||
if(dbgMode_) {
|
||||
printf("STATE = READ_IDLE\n");
|
||||
printf("STATE = READ_IDLE %s\n",name_);
|
||||
printf("All data read from slave SDO.\n");
|
||||
printBuffer();
|
||||
//copy complete data to dataBuffer_
|
||||
printBuffer();
|
||||
}
|
||||
refreshNeeded_ = 1;
|
||||
unlockSdo1();
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
@@ -279,7 +357,7 @@ int ecmcCANOpenSDO::readDataStateMachine(can_frame *frame) {
|
||||
}
|
||||
|
||||
int ecmcCANOpenSDO::writeDataStateMachine(can_frame *frame) {
|
||||
printf("writeDataStateMachine\n");
|
||||
//printf("writeDataStateMachine %s\n",name_);
|
||||
int bytes = 0;
|
||||
switch(writeStates_) {
|
||||
case WRITE_WAIT_FOR_CONF:
|
||||
@@ -294,7 +372,7 @@ int ecmcCANOpenSDO::writeDataStateMachine(can_frame *frame) {
|
||||
writeNextDataToSlave(useTg1Frame_);
|
||||
writeStates_ = WRITE_DATA; //Next frame should be data!
|
||||
if(dbgMode_) {
|
||||
printf("STATE = WRITE_DATA\n");
|
||||
printf("STATE = WRITE_DATA %s\n",name_);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -309,13 +387,13 @@ int ecmcCANOpenSDO::writeDataStateMachine(can_frame *frame) {
|
||||
// Check if write was done already or if more frames are needed!
|
||||
if(writtenBytes_ >= ODSize_) {
|
||||
writeStates_ = WRITE_IDLE;
|
||||
useTg1Frame_ = 0;
|
||||
busy_ = 0;
|
||||
useTg1Frame_ = 0;
|
||||
if(dbgMode_) {
|
||||
printf("STATE = WRITE_IDLE\n");
|
||||
printf("STATE = WRITE_IDLE %s\n",name_);
|
||||
printf("All data written to slave SDO.\n");
|
||||
printBuffer();
|
||||
}
|
||||
unlockSdo1();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -412,49 +490,164 @@ void ecmcCANOpenSDO::setValue(uint8_t *data, size_t bytes) {
|
||||
if(bytesToCopy == 0) {
|
||||
return;
|
||||
}
|
||||
memcpy(dataBuffer_, &data, ODSize_);
|
||||
// always write to tempDatabuffer then transfer
|
||||
epicsMutexLock(dataMutex_);
|
||||
memcpy(tempDataBuffer_, data, ODSize_);
|
||||
epicsMutexUnlock(dataMutex_);
|
||||
}
|
||||
|
||||
int ecmcCANOpenSDO::writeValue() {
|
||||
// Busy right now!
|
||||
printf("WRITEVALUE!!\n");
|
||||
if(busy_ || writeStates_ != WRITE_IDLE ) {
|
||||
//printf("WRITEVALUE %s\n",name_);
|
||||
|
||||
if(busy_) {
|
||||
writePending_ = true;
|
||||
return ECMC_CAN_ERROR_SDO_WRITE_BUSY;
|
||||
}
|
||||
busy_ = 1;
|
||||
|
||||
if(!tryLockSdo1()) {
|
||||
// wait for busy to go down
|
||||
writePending_ = true;
|
||||
return ECMC_CAN_ERROR_SDO_WRITE_BUSY;
|
||||
}
|
||||
|
||||
if(writeStates_ != WRITE_IDLE ) {
|
||||
writePending_ = true;
|
||||
return ECMC_CAN_ERROR_SDO_WRITE_BUSY;
|
||||
}
|
||||
writePending_ = false;
|
||||
|
||||
epicsMutexLock(dataMutex_);
|
||||
memcpy(dataBuffer_, tempDataBuffer_, ODSize_);
|
||||
epicsMutexUnlock(dataMutex_);
|
||||
|
||||
writeStates_ = WRITE_REQ_TRANSFER;
|
||||
if(dbgMode_) {
|
||||
printf("STATE = WRITE_REQ_TRANSFER\n");
|
||||
printf("STATE = WRITE_REQ_TRANSFER %s\n",name_);
|
||||
}
|
||||
|
||||
writeBuffer_->addWriteCAN(&writeReqTransferFrame_);
|
||||
|
||||
writeStates_ = WRITE_WAIT_FOR_CONF;
|
||||
if(dbgMode_) {
|
||||
printf("STATE = WRITE_WAIT_FOR_CONF\n");
|
||||
printf("STATE = WRITE_WAIT_FOR_CONF %s\n",name_);
|
||||
}
|
||||
return 0;
|
||||
// State machine is now in rx frame()
|
||||
}
|
||||
|
||||
//# w 0x603 [8] 0x40 0x40 0x26 0x00 0x00 0x00 0x00 0x00
|
||||
//# w 0x603 [8] 0x61 0x40 0x26 0x00 0x00 0x00 0x00 0x00
|
||||
//# w 0x603 [8] 0x71 0x40 0x26 0x00 0x00 0x00 0x00 0x00
|
||||
//# w 0x603 [8] 0x61 0x40 0x26 0x00 0x00 0x00 0x00 0x00
|
||||
//# w 0x603 [8] 0x71 0x40 0x26 0x00 0x00 0x00 0x00 0x00
|
||||
//# w 0x603 [8] 0x61 0x40 0x26 0x00 0x00 0x00 0x00 0x00
|
||||
//# w 0x603 [8] 0x71 0x40 0x26 0x00 0x00 0x00 0x00 0x00
|
||||
//# w 0x603 [8] 0x61 0x40 0x26 0x00 0x00 0x00 0x00 0x00
|
||||
//#
|
||||
//# 0x38 bytes to recive!!
|
||||
//# r 0x583 [8] 0x41 0x40 0x26 0x00 0x38 0x00 0x00 0x00
|
||||
//# Data below
|
||||
//# r 0x583 [8] 0x00 0x18 0x00 0x24 0x00 0x0E 0x00 0x0A
|
||||
//# r 0x583 [8] 0x10 0x00 0x00 0x00 0xBF 0x00 0x00 0x00
|
||||
//# r 0x583 [8] 0x00 0xC2 0x01 0x00 0x00 0x00 0x00 0x35
|
||||
//# r 0x583 [8] 0x10 0x1C 0x84 0x02 0x46 0x1A 0x3C 0x49
|
||||
//# r 0x583 [8] 0x00 0xC2 0x01 0x00 0x00 0x00 0x00 0x00
|
||||
//# r 0x583 [8] 0x10 0x00 0xC8 0x48 0x51 0x2F 0x00 0x00
|
||||
//# r 0x583 [8] 0x00 0x5C 0x2D 0x81 0x14 0x67 0x0D 0xA6
|
||||
//
|
||||
int ecmcCANOpenSDO::tryLockSdo1() {
|
||||
epicsMutexLock(getLockMutex_);
|
||||
if(busy_) {
|
||||
epicsMutexUnlock(getLockMutex_);
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool prevLock = ptrSdo1Lock_->test_and_set();
|
||||
if(prevLock) {
|
||||
// wait for busy to go down
|
||||
epicsMutexUnlock(getLockMutex_);
|
||||
return 0;
|
||||
}
|
||||
|
||||
busy_ = true;
|
||||
epicsMutexUnlock(getLockMutex_);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int ecmcCANOpenSDO::unlockSdo1() {
|
||||
epicsMutexLock(getLockMutex_);
|
||||
if(busy_) {
|
||||
ptrSdo1Lock_->clear();
|
||||
busy_ = false;
|
||||
}
|
||||
epicsMutexUnlock(getLockMutex_);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ecmcCANOpenSDO::initAsyn() {
|
||||
|
||||
ecmcAsynPortDriver *ecmcAsynPort = (ecmcAsynPortDriver *)getEcmcAsynPortDriver();
|
||||
if(!ecmcAsynPort) {
|
||||
printf("ERROR: ecmcAsynPort NULL.");
|
||||
throw std::runtime_error( "ERROR: ecmcAsynPort NULL." );
|
||||
}
|
||||
|
||||
// Add resultdata "plugin.can.dev%d.<name>"
|
||||
std::string paramName = ECMC_PLUGIN_ASYN_PREFIX + std::string(".dev") +
|
||||
to_string(nodeId_) + ".sdo" /*+ to_string(objIndex_) */
|
||||
+ "." + std::string(name_);
|
||||
|
||||
dataParam_ = ecmcAsynPort->addNewAvailParam(
|
||||
paramName.c_str(), // name
|
||||
asynParamInt8Array, // asyn type
|
||||
dataBuffer_, // pointer to data
|
||||
ODSize_, // size of data
|
||||
ECMC_EC_U8, // ecmc data type
|
||||
0); // die if fail
|
||||
|
||||
if(!dataParam_) {
|
||||
printf("ERROR: Failed create asyn param for data.");
|
||||
throw std::runtime_error( "ERROR: Failed create asyn param for data: " + paramName);
|
||||
}
|
||||
|
||||
// Allow different types depending on size
|
||||
if(ODSize_>1){
|
||||
dataParam_->addSupportedAsynType(asynParamInt16Array);
|
||||
}
|
||||
if(ODSize_>3){
|
||||
dataParam_->addSupportedAsynType(asynParamInt32Array);
|
||||
dataParam_->addSupportedAsynType(asynParamFloat32Array);
|
||||
dataParam_->addSupportedAsynType(asynParamInt32);
|
||||
}
|
||||
if(ODSize_>7){
|
||||
dataParam_->addSupportedAsynType(asynParamFloat64Array);
|
||||
dataParam_->addSupportedAsynType(asynParamFloat64);
|
||||
}
|
||||
|
||||
dataParam_->setAllowWriteToEcmc(rw_ == DIR_WRITE);
|
||||
|
||||
if(rw_ == DIR_WRITE) {
|
||||
dataParam_->setExeCmdFunctPtr(asynWriteSDOValue,this);
|
||||
}
|
||||
|
||||
dataParam_->refreshParam(1); // read once into asyn param lib
|
||||
ecmcAsynPort->callParamCallbacks(ECMC_ASYN_DEFAULT_LIST, ECMC_ASYN_DEFAULT_ADDR);
|
||||
|
||||
// Add resultdata "plugin.can.dev%d.error"
|
||||
paramName = ECMC_PLUGIN_ASYN_PREFIX + std::string(".dev") +
|
||||
to_string(nodeId_) + ".sdo" + "." + std::string(name_) + std::string(".error");
|
||||
|
||||
errorParam_ = ecmcAsynPort->addNewAvailParam(
|
||||
paramName.c_str(), // name
|
||||
asynParamInt32, // asyn type
|
||||
(uint8_t*)&errorCode_, // pointer to data
|
||||
sizeof(errorCode_), // size of data
|
||||
ECMC_EC_U32, // ecmc data type
|
||||
0); // die if fail
|
||||
|
||||
if(!errorParam_) {
|
||||
printf("ERROR: Failed create asyn param for data.");
|
||||
throw std::runtime_error( "ERROR: Failed create asyn param for data: " + paramName);
|
||||
}
|
||||
|
||||
errorParam_->setAllowWriteToEcmc(false); // need to callback here
|
||||
errorParam_->refreshParam(1); // read once into asyn param lib
|
||||
ecmcAsynPort->callParamCallbacks(ECMC_ASYN_DEFAULT_LIST, ECMC_ASYN_DEFAULT_ADDR);
|
||||
|
||||
}
|
||||
|
||||
void ecmcCANOpenSDO::refreshAsynParams() {
|
||||
if(refreshNeeded_) {
|
||||
dataParam_->refreshParamRT(1); // read once into asyn param lib
|
||||
errorParam_->refreshParamRT(1); // read once into asyn param lib
|
||||
}
|
||||
refreshNeeded_ = 0;
|
||||
}
|
||||
|
||||
// Avoid issues with std:to_string()
|
||||
std::string ecmcCANOpenSDO::to_string(int value) {
|
||||
std::ostringstream os;
|
||||
os << value;
|
||||
return os.str();
|
||||
}
|
||||
@@ -20,25 +20,31 @@
|
||||
#include "inttypes.h"
|
||||
#include <string>
|
||||
#include "ecmcSocketCANWriteBuffer.h"
|
||||
#include "ecmcDataItem.h"
|
||||
|
||||
#include <linux/can.h>
|
||||
#include <linux/can/raw.h>
|
||||
|
||||
#define ECMC_CAN_ERROR_SDO_WRITE_BUSY 110
|
||||
|
||||
#define ECMC_CAN_ERROR_SDO_TIMEOUT 111
|
||||
|
||||
|
||||
class ecmcCANOpenSDO {
|
||||
public:
|
||||
ecmcCANOpenSDO(ecmcSocketCANWriteBuffer* writeBuffer,
|
||||
uint32_t nodeId,
|
||||
uint32_t cobIdTx, // 0x580 + CobId
|
||||
uint32_t cobIdRx, // 0x600 + Cobid
|
||||
ecmc_can_direction rw,
|
||||
uint16_t ODIndex, // Object dictionary index
|
||||
uint8_t ODSubIndex, // Object dictionary subindex
|
||||
uint32_t ODSize,
|
||||
int readSampleTimeMs,
|
||||
int exeSampleTimeMs,
|
||||
int readSampleTimeMs,
|
||||
int exeSampleTimeMs,
|
||||
const char *name,
|
||||
std::atomic_flag *ptrSdo1Lock,
|
||||
int objIndex,
|
||||
int dbgMode);
|
||||
~ecmcCANOpenSDO();
|
||||
void execute();
|
||||
@@ -52,8 +58,11 @@ class ecmcCANOpenSDO {
|
||||
int writeDataStateMachine(can_frame *frame);
|
||||
int writeNextDataToSlave(int useToggle);
|
||||
int writeWaitForDataConfFrame(int useToggle, can_frame *frame);
|
||||
|
||||
int tryLockSdo1();
|
||||
int unlockSdo1();
|
||||
|
||||
ecmcSocketCANWriteBuffer *writeBuffer_;
|
||||
uint32_t nodeId_; // with cobid
|
||||
uint32_t cobIdRx_; // with cobid
|
||||
uint32_t cobIdTx_; // with cobid
|
||||
int readSampleTimeMs_;
|
||||
@@ -77,14 +86,35 @@ class ecmcCANOpenSDO {
|
||||
can_frame writeConfReqFrameTg1_;
|
||||
|
||||
int dbgMode_;
|
||||
int busy_;
|
||||
int errorCode_;
|
||||
int objIndex_;
|
||||
|
||||
uint8_t *dataBuffer_;
|
||||
uint8_t *tempDataBuffer_;
|
||||
uint32_t recivedBytes_;
|
||||
int useTg1Frame_;
|
||||
ecmc_read_states readStates_;
|
||||
ecmc_write_states writeStates_;
|
||||
void printBuffer();
|
||||
uint32_t writtenBytes_;
|
||||
char *name_;
|
||||
epicsMutexId dataMutex_;
|
||||
epicsMutexId getLockMutex_;
|
||||
int busyCounter_;
|
||||
std::atomic_flag *ptrSdo1Lock_;
|
||||
bool busy_;
|
||||
bool writePending_;
|
||||
|
||||
static std::string to_string(int value);
|
||||
|
||||
//ASYN
|
||||
void initAsyn();
|
||||
void refreshAsynParams();
|
||||
int refreshNeeded_;
|
||||
ecmcAsynDataItem *dataParam_;
|
||||
ecmcAsynDataItem *errorParam_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif /* ECMC_CANOPEN_SDO_H_ */
|
||||
@@ -7,6 +7,7 @@
|
||||
*
|
||||
* Created on: Mar 21, 2020
|
||||
* Author: anderssandstrom
|
||||
* Credits to https://github.com/sgreg/dynamic-loading
|
||||
*
|
||||
\*************************************************************************/
|
||||
|
||||
484
src/ecmcSocketCAN.cpp
Normal file
484
src/ecmcSocketCAN.cpp
Normal file
@@ -0,0 +1,484 @@
|
||||
/*************************************************************************\
|
||||
* 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.
|
||||
*
|
||||
* ecmcSocketCAN.cpp
|
||||
*
|
||||
* Created on: Mar 22, 2020
|
||||
* Author: anderssandstrom
|
||||
* Credits to https://github.com/sgreg/dynamic-loading
|
||||
*
|
||||
\*************************************************************************/
|
||||
|
||||
// Needed to get headers in ecmc right...
|
||||
#define ECMC_IS_PLUGIN
|
||||
|
||||
#include <sstream>
|
||||
#include "ecmcSocketCAN.h"
|
||||
#include "ecmcPluginClient.h"
|
||||
#include "ecmcAsynPortDriver.h"
|
||||
#include "ecmcAsynPortDriverUtils.h"
|
||||
#include "epicsThread.h"
|
||||
|
||||
// Start worker for socket read()
|
||||
void f_worker_read(void *obj) {
|
||||
if(!obj) {
|
||||
printf("%s/%s:%d: Error: Worker read thread ecmcSocketCAN object NULL..\n",
|
||||
__FILE__, __FUNCTION__, __LINE__);
|
||||
return;
|
||||
}
|
||||
ecmcSocketCAN * canObj = (ecmcSocketCAN*)obj;
|
||||
canObj->doReadWorker();
|
||||
}
|
||||
|
||||
// Start worker for socket connect()
|
||||
void f_worker_connect(void *obj) {
|
||||
if(!obj) {
|
||||
printf("%s/%s:%d: Error: Worker connect thread ecmcSocketCAN object NULL..\n",
|
||||
__FILE__, __FUNCTION__, __LINE__);
|
||||
return;
|
||||
}
|
||||
ecmcSocketCAN * canObj = (ecmcSocketCAN*)obj;
|
||||
canObj->doConnectWorker();
|
||||
}
|
||||
|
||||
/** ecmc ecmcSocketCAN class
|
||||
* This object can throw:
|
||||
* - bad_alloc
|
||||
* - invalid_argument
|
||||
* - runtime_error
|
||||
*/
|
||||
ecmcSocketCAN::ecmcSocketCAN(char* configStr,
|
||||
char* portName,
|
||||
int exeSampleTimeMs) {
|
||||
// Init
|
||||
cfgCanIFStr_ = NULL;
|
||||
cfgDbgMode_ = 0;
|
||||
cfgAutoConnect_ = 1;
|
||||
destructs_ = 0;
|
||||
socketId_ = -1;
|
||||
connected_ = 0;
|
||||
writeBuffer_ = NULL;
|
||||
deviceCounter_ = 0;
|
||||
refreshNeeded_ = 0;
|
||||
errorCode_ = 0;
|
||||
masterDev_ = NULL;
|
||||
for(int i = 0; i<ECMC_CAN_MAX_DEVICES;i++) {
|
||||
devices_[i] = NULL;
|
||||
}
|
||||
|
||||
exeSampleTimeMs_ = exeSampleTimeMs;
|
||||
|
||||
memset(&ifr_,0,sizeof(struct ifreq));
|
||||
memset(&rxmsg_,0,sizeof(struct can_frame));
|
||||
memset(&addr_,0,sizeof(struct sockaddr_can));
|
||||
|
||||
parseConfigStr(configStr); // Assigns all configs
|
||||
// Check valid nfft
|
||||
if(!cfgCanIFStr_ ) {
|
||||
throw std::out_of_range("CAN inteface must be defined (can0, vcan0...).");
|
||||
}
|
||||
|
||||
// Create worker thread for reading socket
|
||||
std::string threadname = "ecmc." ECMC_PLUGIN_ASYN_PREFIX".read";
|
||||
if(epicsThreadCreate(threadname.c_str(), 0, 32768, f_worker_read, this) == NULL) {
|
||||
throw std::runtime_error("Error: Failed create worker thread for read().");
|
||||
}
|
||||
|
||||
// Create worker thread for connecting socket
|
||||
threadname = "ecmc." ECMC_PLUGIN_ASYN_PREFIX".connect";
|
||||
if(epicsThreadCreate(threadname.c_str(), 0, 32768, f_worker_connect, this) == NULL) {
|
||||
throw std::runtime_error("Error: Failed create worker thread for connect().");
|
||||
}
|
||||
|
||||
if(cfgAutoConnect_) {
|
||||
connectPrivate();
|
||||
}
|
||||
writeBuffer_ = new ecmcSocketCANWriteBuffer(socketId_, cfgDbgMode_);
|
||||
initAsyn();
|
||||
}
|
||||
|
||||
ecmcSocketCAN::~ecmcSocketCAN() {
|
||||
// kill worker
|
||||
destructs_ = 1; // maybe need todo in other way..
|
||||
doWriteEvent_.signal();
|
||||
doConnectEvent_.signal();
|
||||
|
||||
for(int i = 0; i<ECMC_CAN_MAX_DEVICES;i++) {
|
||||
delete devices_[i];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void ecmcSocketCAN::parseConfigStr(char *configStr) {
|
||||
|
||||
// check config parameters
|
||||
if (configStr && configStr[0]) {
|
||||
char *pOptions = strdup(configStr);
|
||||
char *pThisOption = pOptions;
|
||||
char *pNextOption = pOptions;
|
||||
|
||||
while (pNextOption && pNextOption[0]) {
|
||||
pNextOption = strchr(pNextOption, ';');
|
||||
if (pNextOption) {
|
||||
*pNextOption = '\0'; /* Terminate */
|
||||
pNextOption++; /* Jump to (possible) next */
|
||||
}
|
||||
|
||||
// ECMC_PLUGIN_DBG_PRINT_OPTION_CMD (1/0)
|
||||
if (!strncmp(pThisOption, ECMC_PLUGIN_DBG_PRINT_OPTION_CMD, strlen(ECMC_PLUGIN_DBG_PRINT_OPTION_CMD))) {
|
||||
pThisOption += strlen(ECMC_PLUGIN_DBG_PRINT_OPTION_CMD);
|
||||
cfgDbgMode_ = atoi(pThisOption);
|
||||
}
|
||||
|
||||
// ECMC_PLUGIN_CONNECT_OPTION_CMD (1/0)
|
||||
if (!strncmp(pThisOption, ECMC_PLUGIN_CONNECT_OPTION_CMD, strlen(ECMC_PLUGIN_CONNECT_OPTION_CMD))) {
|
||||
pThisOption += strlen(ECMC_PLUGIN_DBG_PRINT_OPTION_CMD);
|
||||
cfgAutoConnect_ = atoi(pThisOption);
|
||||
}
|
||||
|
||||
// ECMC_PLUGIN_IF_OPTION_CMD (Source string)
|
||||
else if (!strncmp(pThisOption, ECMC_PLUGIN_IF_OPTION_CMD, strlen(ECMC_PLUGIN_IF_OPTION_CMD))) {
|
||||
pThisOption += strlen(ECMC_PLUGIN_IF_OPTION_CMD);
|
||||
cfgCanIFStr_=strdup(pThisOption);
|
||||
}
|
||||
|
||||
pThisOption = pNextOption;
|
||||
}
|
||||
free(pOptions);
|
||||
}
|
||||
if(!cfgCanIFStr_) {
|
||||
throw std::invalid_argument( "CAN interface not defined.");
|
||||
}
|
||||
}
|
||||
|
||||
// For connect commands over asyn or plc. let worker connect
|
||||
void ecmcSocketCAN::connectExternal() {
|
||||
if(!connected_) {
|
||||
doConnectEvent_.signal(); // let worker start
|
||||
}
|
||||
}
|
||||
|
||||
void ecmcSocketCAN::connectPrivate() {
|
||||
|
||||
if((socketId_ = socket(PF_CAN, SOCK_RAW, CAN_RAW)) == -1) {
|
||||
throw std::runtime_error( "Error while opening socket.");
|
||||
return;
|
||||
}
|
||||
|
||||
strcpy(ifr_.ifr_name, cfgCanIFStr_);
|
||||
ioctl(socketId_, SIOCGIFINDEX, &ifr_);
|
||||
|
||||
addr_.can_family = AF_CAN;
|
||||
addr_.can_ifindex = ifr_.ifr_ifindex;
|
||||
|
||||
printf("%s at index %d\n", cfgCanIFStr_, ifr_.ifr_ifindex);
|
||||
|
||||
if(bind(socketId_, (struct sockaddr *)&addr_, sizeof(addr_)) == -1) {
|
||||
throw std::runtime_error( "Error in socket bind.");
|
||||
return;
|
||||
}
|
||||
connected_ = 1;
|
||||
}
|
||||
|
||||
int ecmcSocketCAN::getConnected() {
|
||||
return connected_;
|
||||
}
|
||||
|
||||
// Read socket worker
|
||||
void ecmcSocketCAN::doReadWorker() {
|
||||
|
||||
while(true) {
|
||||
|
||||
if(destructs_) {
|
||||
break;
|
||||
}
|
||||
if(!connected_) {
|
||||
timespec tempPauseTime;
|
||||
tempPauseTime.tv_sec = 1;
|
||||
tempPauseTime.tv_nsec = 0;
|
||||
nanosleep(&tempPauseTime,NULL);
|
||||
continue;
|
||||
}
|
||||
// Wait for new CAN frame
|
||||
int bytes = read(socketId_, &rxmsg_, sizeof(rxmsg_));
|
||||
|
||||
// error in read()
|
||||
if(bytes == -1) {
|
||||
errorCode_ = errno;
|
||||
printf("ecmcSocketCAN: read() fail with error: %s, (0x%x).\n", strerror(errno),errno);
|
||||
refreshNeeded_ = 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// incomplete read()
|
||||
if(bytes != sizeof(rxmsg_)) {
|
||||
printf("ecmcSocketCAN: read() fail with error: Incomplete read, not a full can frame (0x%x).\n",ECMC_CAN_ERROR_READ_INCOMPLETE);
|
||||
errorCode_ = ECMC_CAN_ERROR_READ_INCOMPLETE;
|
||||
refreshNeeded_ = 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// forward all data to devices (including master)
|
||||
for(int i = 0; i < deviceCounter_; i++){
|
||||
devices_[i]->newRxFrame(&rxmsg_);
|
||||
}
|
||||
|
||||
if(cfgDbgMode_) {
|
||||
// Simulate candump printout
|
||||
printf("r 0x%03X", rxmsg_.can_id);
|
||||
printf(" [%d]", rxmsg_.can_dlc);
|
||||
for(int i=0; i<rxmsg_.can_dlc; i++ ) {
|
||||
printf(" 0x%02X", rxmsg_.data[i]);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Connect socket worker
|
||||
void ecmcSocketCAN::doConnectWorker() {
|
||||
|
||||
while(true) {
|
||||
|
||||
if(destructs_) {
|
||||
return;
|
||||
}
|
||||
doConnectEvent_.wait();
|
||||
if(destructs_) {
|
||||
return;
|
||||
}
|
||||
connectPrivate();
|
||||
}
|
||||
}
|
||||
|
||||
int ecmcSocketCAN::getlastWritesError() {
|
||||
if(!writeBuffer_) {
|
||||
return ECMC_CAN_ERROR_WRITE_BUFFER_NULL;
|
||||
}
|
||||
return writeBuffer_->getlastWritesErrorAndReset();
|
||||
}
|
||||
|
||||
int ecmcSocketCAN::addWriteCAN(uint32_t canId,
|
||||
uint8_t len,
|
||||
uint8_t data0,
|
||||
uint8_t data1,
|
||||
uint8_t data2,
|
||||
uint8_t data3,
|
||||
uint8_t data4,
|
||||
uint8_t data5,
|
||||
uint8_t data6,
|
||||
uint8_t data7) {
|
||||
|
||||
if(!writeBuffer_) {
|
||||
return ECMC_CAN_ERROR_WRITE_BUFFER_NULL;
|
||||
}
|
||||
|
||||
writeBuffer_->addWriteCAN(canId,
|
||||
len,
|
||||
data0,
|
||||
data1,
|
||||
data2,
|
||||
data3,
|
||||
data4,
|
||||
data5,
|
||||
data6,
|
||||
data7);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ecmcSocketCAN::execute() {
|
||||
|
||||
for(int i = 0; i < deviceCounter_; i++){
|
||||
devices_[i]->execute();
|
||||
}
|
||||
|
||||
int writeError=getlastWritesError();
|
||||
if (writeError) {
|
||||
errorCode_ = writeError;
|
||||
refreshNeeded_ = 1;
|
||||
}
|
||||
refreshAsynParams();
|
||||
return;
|
||||
}
|
||||
|
||||
// Avoid issues with std:to_string()
|
||||
std::string ecmcSocketCAN::to_string(int value) {
|
||||
std::ostringstream os;
|
||||
os << value;
|
||||
return os.str();
|
||||
}
|
||||
|
||||
void ecmcSocketCAN::addMaster(uint32_t nodeId,
|
||||
const char* name,
|
||||
int lssSampleTimeMs,
|
||||
int syncSampleTimeMs,
|
||||
int heartSampleTimeMs) {
|
||||
|
||||
if(masterDev_) {
|
||||
throw std::runtime_error("Master already added.");
|
||||
}
|
||||
if(deviceCounter_ >= ECMC_CAN_MAX_DEVICES) {
|
||||
throw std::out_of_range("Device array full.");
|
||||
}
|
||||
if(nodeId >= 128) {
|
||||
throw std::out_of_range("Node id out of range.");
|
||||
}
|
||||
|
||||
if(lssSampleTimeMs <= 0) {
|
||||
throw std::out_of_range("LSS sample time ms out of range.");
|
||||
}
|
||||
|
||||
if(syncSampleTimeMs <= 0) {
|
||||
throw std::out_of_range("Sync sample time ms out of range.");
|
||||
}
|
||||
|
||||
if(heartSampleTimeMs <= 0) {
|
||||
throw std::out_of_range("Heart sample time ms out of range.");
|
||||
}
|
||||
|
||||
masterDev_ = new ecmcCANOpenMaster(writeBuffer_,
|
||||
nodeId,
|
||||
exeSampleTimeMs_,
|
||||
lssSampleTimeMs,
|
||||
syncSampleTimeMs,
|
||||
heartSampleTimeMs,
|
||||
name,
|
||||
cfgDbgMode_);
|
||||
// add as a normal device also for execute and rxframe
|
||||
devices_[deviceCounter_] = masterDev_;
|
||||
deviceCounter_++;
|
||||
}
|
||||
|
||||
void ecmcSocketCAN::addDevice(uint32_t nodeId,
|
||||
const char* name,
|
||||
int heartTimeoutMs){
|
||||
if(deviceCounter_ >= ECMC_CAN_MAX_DEVICES) {
|
||||
throw std::out_of_range("Device array full.");
|
||||
}
|
||||
if(nodeId >= 128) {
|
||||
throw std::out_of_range("Node id out of range.");
|
||||
}
|
||||
|
||||
devices_[deviceCounter_] = new ecmcCANOpenDevice(writeBuffer_,nodeId,exeSampleTimeMs_,name,heartTimeoutMs,cfgDbgMode_);
|
||||
deviceCounter_++;
|
||||
}
|
||||
|
||||
int ecmcSocketCAN::findDeviceWithNodeId(uint32_t nodeId) {
|
||||
for(int i=0; i < deviceCounter_;i++) {
|
||||
if(devices_[i]) {
|
||||
if(devices_[i]->getNodeId() == nodeId) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void ecmcSocketCAN::addPDO(uint32_t nodeId,
|
||||
uint32_t cobId,
|
||||
ecmc_can_direction rw,
|
||||
uint32_t ODSize,
|
||||
int readTimeoutMs,
|
||||
int writeCycleMs, //if <0 then write on demand.
|
||||
const char* name) {
|
||||
int devId = findDeviceWithNodeId(nodeId);
|
||||
if(devId < 0) {
|
||||
throw std::out_of_range("Node id not found in any configured device.");
|
||||
}
|
||||
|
||||
int errorCode = devices_[devId]->addPDO(cobId,
|
||||
rw,
|
||||
ODSize,
|
||||
readTimeoutMs,
|
||||
writeCycleMs,
|
||||
name);
|
||||
if(errorCode > 0) {
|
||||
throw std::runtime_error("AddPDO() failed.");
|
||||
}
|
||||
}
|
||||
|
||||
void ecmcSocketCAN::addSDO(uint32_t nodeId,
|
||||
uint32_t cobIdTx, // 0x580 + CobId
|
||||
uint32_t cobIdRx, // 0x600 + Cobid
|
||||
ecmc_can_direction rw,
|
||||
uint16_t ODIndex, // Object dictionary index
|
||||
uint8_t ODSubIndex, // Object dictionary subindex
|
||||
uint32_t ODSize,
|
||||
int readSampleTimeMs,
|
||||
const char* name) {
|
||||
|
||||
int devId = findDeviceWithNodeId(nodeId);
|
||||
if(devId < 0) {
|
||||
throw std::out_of_range("Node id not found in any configured device.");
|
||||
}
|
||||
|
||||
int errorCode = devices_[devId]->addSDO(cobIdTx,
|
||||
cobIdRx,
|
||||
rw,
|
||||
ODIndex,
|
||||
ODSubIndex,
|
||||
ODSize,
|
||||
readSampleTimeMs,
|
||||
name);
|
||||
if(errorCode > 0) {
|
||||
throw std::runtime_error("AddSDO() failed.");
|
||||
}
|
||||
}
|
||||
|
||||
void ecmcSocketCAN::initAsyn() {
|
||||
|
||||
ecmcAsynPortDriver *ecmcAsynPort = (ecmcAsynPortDriver *)getEcmcAsynPortDriver();
|
||||
if(!ecmcAsynPort) {
|
||||
printf("ERROR: ecmcAsynPort NULL.");
|
||||
throw std::runtime_error( "ERROR: ecmcAsynPort NULL." );
|
||||
}
|
||||
|
||||
// Add resultdata "plugin.can.read.error"
|
||||
std::string paramName = ECMC_PLUGIN_ASYN_PREFIX + std::string(".com.error");
|
||||
|
||||
errorParam_ = ecmcAsynPort->addNewAvailParam(
|
||||
paramName.c_str(), // name
|
||||
asynParamInt32, // asyn type
|
||||
(uint8_t*)&errorCode_, // pointer to data
|
||||
sizeof(errorCode_), // size of data
|
||||
ECMC_EC_U32, // ecmc data type
|
||||
0); // die if fail
|
||||
|
||||
if(!errorParam_) {
|
||||
printf("ERROR: Failed create asyn param for data.");
|
||||
throw std::runtime_error( "ERROR: Failed create asyn param for: " + paramName);
|
||||
}
|
||||
errorParam_->setAllowWriteToEcmc(false); // need to callback here
|
||||
errorParam_->refreshParam(1); // read once into asyn param lib
|
||||
ecmcAsynPort->callParamCallbacks(ECMC_ASYN_DEFAULT_LIST, ECMC_ASYN_DEFAULT_ADDR);
|
||||
|
||||
// Add resultdata "plugin.can.read.connected"
|
||||
paramName = ECMC_PLUGIN_ASYN_PREFIX + std::string(".com.connected");
|
||||
|
||||
connectedParam_ = ecmcAsynPort->addNewAvailParam(
|
||||
paramName.c_str(), // name
|
||||
asynParamInt32, // asyn type
|
||||
(uint8_t*)&connected_, // pointer to data
|
||||
sizeof(connected_), // size of data
|
||||
ECMC_EC_U32, // ecmc data type
|
||||
0); // die if fail
|
||||
|
||||
if(!connectedParam_) {
|
||||
printf("ERROR: Failed create asyn param for connected.");
|
||||
throw std::runtime_error( "ERROR: Failed create asyn param for: " + paramName);
|
||||
}
|
||||
connectedParam_->setAllowWriteToEcmc(false); // need to callback here
|
||||
connectedParam_->refreshParam(1); // read once into asyn param lib
|
||||
ecmcAsynPort->callParamCallbacks(ECMC_ASYN_DEFAULT_LIST, ECMC_ASYN_DEFAULT_ADDR);
|
||||
}
|
||||
|
||||
// only refresh from "execute" thread
|
||||
void ecmcSocketCAN::refreshAsynParams() {
|
||||
if(refreshNeeded_) {
|
||||
connectedParam_->refreshParamRT(1); // read once into asyn param lib
|
||||
errorParam_->refreshParamRT(1); // read once into asyn param lib
|
||||
}
|
||||
refreshNeeded_ = 0;
|
||||
}
|
||||
3
src/ecmcSocketCAN.dbd
Normal file
3
src/ecmcSocketCAN.dbd
Normal file
@@ -0,0 +1,3 @@
|
||||
registrar("ecmcCANPluginDriverRegister")
|
||||
function(ecmcByteToArrayInit)
|
||||
function(ecmcByteToArray)
|
||||
@@ -17,8 +17,8 @@
|
||||
#include "ecmcAsynPortDriver.h"
|
||||
#include "ecmcSocketCANDefs.h"
|
||||
#include "ecmcSocketCANWriteBuffer.h"
|
||||
#include "ecmcCANOpenSDO.h"
|
||||
#include "ecmcCANOpenPDO.h"
|
||||
#include "ecmcCANOpenDevice.h"
|
||||
#include "ecmcCANOpenMaster.h"
|
||||
#include "inttypes.h"
|
||||
#include <string>
|
||||
|
||||
@@ -36,13 +36,15 @@
|
||||
#include <linux/can/raw.h>
|
||||
|
||||
#define ECMC_CAN_MAX_WRITE_CMDS 128
|
||||
#define ECMC_CAN_MAX_DEVICES 128
|
||||
#define ECMC_CAN_ERROR_WRITE_FULL 10
|
||||
#define ECMC_CAN_ERROR_WRITE_BUSY 11
|
||||
#define ECMC_CAN_ERROR_WRITE_NO_DATA 12
|
||||
#define ECMC_CAN_ERROR_WRITE_INCOMPLETE 13
|
||||
#define ECMC_CAN_ERROR_WRITE_BUFFER_NULL 14
|
||||
#define ECMC_CAN_ERROR_READ_INCOMPLETE 15
|
||||
|
||||
class ecmcSocketCAN : public asynPortDriver {
|
||||
class ecmcSocketCAN {
|
||||
public:
|
||||
|
||||
/** ecmc ecmcSocketCAN class
|
||||
@@ -61,11 +63,11 @@ class ecmcSocketCAN : public asynPortDriver {
|
||||
void doWriteWorker();
|
||||
void doConnectWorker();
|
||||
|
||||
virtual asynStatus writeInt32(asynUser *pasynUser, epicsInt32 value);
|
||||
virtual asynStatus readInt32(asynUser *pasynUser, epicsInt32 *value);
|
||||
virtual asynStatus readInt8Array(asynUser *pasynUser, epicsInt8 *value,
|
||||
size_t nElements, size_t *nIn);
|
||||
virtual asynStatus readFloat64(asynUser *pasynUser, epicsFloat64 *value);
|
||||
//virtual asynStatus writeInt32(asynUser *pasynUser, epicsInt32 value);
|
||||
//virtual asynStatus readInt32(asynUser *pasynUser, epicsInt32 *value);
|
||||
//virtual asynStatus readInt8Array(asynUser *pasynUser, epicsInt8 *value,
|
||||
// size_t nElements, size_t *nIn);
|
||||
//virtual asynStatus readFloat64(asynUser *pasynUser, epicsFloat64 *value);
|
||||
void connectExternal();
|
||||
int getConnected();
|
||||
int addWriteCAN(uint32_t canId,
|
||||
@@ -80,10 +82,39 @@ class ecmcSocketCAN : public asynPortDriver {
|
||||
uint8_t data7);
|
||||
int getlastWritesError();
|
||||
void execute(); // ecmc rt loop
|
||||
|
||||
void addMaster(uint32_t nodeId,
|
||||
const char* name,
|
||||
int lssSampleTimeMs,
|
||||
int syncSampleTimeMs,
|
||||
int heartSampleTimeMs);
|
||||
|
||||
void addDevice(uint32_t nodeId,
|
||||
const char* name,
|
||||
int heartTimeoutMs);
|
||||
|
||||
void addPDO(uint32_t nodeId,
|
||||
uint32_t cobId,
|
||||
ecmc_can_direction rw,
|
||||
uint32_t ODSize,
|
||||
int readTimeoutMs,
|
||||
int writeCycleMs, //if <0 then write on demand.
|
||||
const char* name);
|
||||
|
||||
void addSDO(uint32_t nodeId,
|
||||
uint32_t cobIdTx, // 0x580 + CobId
|
||||
uint32_t cobIdRx, // 0x600 + Cobid
|
||||
ecmc_can_direction rw,
|
||||
uint16_t ODIndex, // Object dictionary index
|
||||
uint8_t ODSubIndex, // Object dictionary subindex
|
||||
uint32_t ODSize,
|
||||
int readSampleTimeMs,
|
||||
const char* name);
|
||||
|
||||
int findDeviceWithNodeId(uint32_t nodeId);
|
||||
|
||||
private:
|
||||
void parseConfigStr(char *configStr);
|
||||
void initAsyn();
|
||||
static std::string to_string(int value);
|
||||
void connectPrivate();
|
||||
int writeCAN(can_frame *frame);
|
||||
@@ -99,19 +130,20 @@ class ecmcSocketCAN : public asynPortDriver {
|
||||
int socketId_;
|
||||
struct sockaddr_can addr_;
|
||||
struct can_frame txmsgBuffer_[ECMC_CAN_MAX_WRITE_CMDS];
|
||||
int writeCmdCounter_;
|
||||
int writeBusy_;
|
||||
int lastWriteSumError_;
|
||||
int exeSampleTimeMs_;
|
||||
|
||||
ecmcSocketCANWriteBuffer *writeBuffer_;
|
||||
ecmcCANOpenSDO *testSdo_;
|
||||
ecmcCANOpenPDO *testPdo_;
|
||||
ecmcCANOpenPDO *lssPdo_;
|
||||
ecmcCANOpenPDO *syncPdo_;
|
||||
ecmcCANOpenPDO *heartPdo_;
|
||||
ecmcCANOpenSDO *basicConfSdo_;
|
||||
int cycleCounter_;
|
||||
|
||||
int deviceCounter_;
|
||||
ecmcCANOpenDevice *devices_[ECMC_CAN_MAX_DEVICES];
|
||||
ecmcCANOpenMaster *masterDev_;
|
||||
|
||||
int errorCode_;
|
||||
int refreshNeeded_;
|
||||
//ASYN
|
||||
void initAsyn();
|
||||
void refreshAsynParams();
|
||||
ecmcAsynDataItem *errorParam_;
|
||||
ecmcAsynDataItem *connectedParam_;
|
||||
};
|
||||
|
||||
#endif /* ECMC_SOCKETCAN_H_ */
|
||||
@@ -7,7 +7,6 @@
|
||||
*
|
||||
* Created on: March 02, 2021
|
||||
* Author: anderssandstrom
|
||||
* Credits to https://github.com/sgreg/dynamic-loading
|
||||
*
|
||||
\*************************************************************************/
|
||||
|
||||
@@ -19,15 +18,26 @@
|
||||
#define ECMC_PLUGIN_IF_OPTION_CMD "IF="
|
||||
#define ECMC_PLUGIN_CONNECT_OPTION_CMD "CONNECT="
|
||||
|
||||
#define ECMC_CANOPEN_NMT_BASE 0x700
|
||||
#define ECMC_CANOPEN_NMT_BOOT 0x0
|
||||
#define ECMC_CANOPEN_NMT_STOP 0x4
|
||||
#define ECMC_CANOPEN_NMT_OP 0x5
|
||||
#define ECMC_CANOPEN_NMT_PREOP 0x7F
|
||||
|
||||
#define ECMC_SDO_REPLY_TIMOUT_MS 1000
|
||||
|
||||
#define ECMC_PLUGIN_ASYN_PREFIX "plugin.can"
|
||||
|
||||
enum ecmc_can_direction {
|
||||
DIR_READ,
|
||||
DIR_WRITE };
|
||||
DIR_WRITE = 1,
|
||||
DIR_READ = 2 };
|
||||
|
||||
enum ecmc_read_states {
|
||||
READ_IDLE,
|
||||
READ_REQ_TRANSFER,
|
||||
READ_WAIT_FOR_CONF,
|
||||
READ_WAIT_FOR_DATA};
|
||||
READ_WAIT_FOR_DATA
|
||||
};
|
||||
|
||||
enum ecmc_write_states {
|
||||
WRITE_IDLE,
|
||||
@@ -36,6 +46,14 @@ enum ecmc_write_states {
|
||||
WRITE_DATA,
|
||||
};
|
||||
|
||||
enum ecmc_nmt_state_act {
|
||||
NMT_NOT_VALID = 0,
|
||||
NMT_BOOT_UP = 1,
|
||||
NMT_STOPPED = 2,
|
||||
NMT_OP = 3,
|
||||
NMT_PREOP = 4
|
||||
};
|
||||
|
||||
struct ODIndexBytes {
|
||||
char byte0:8;
|
||||
char byte1:8;
|
||||
560
src/ecmcSocketCANWrap.cpp
Normal file
560
src/ecmcSocketCANWrap.cpp
Normal file
@@ -0,0 +1,560 @@
|
||||
/*************************************************************************\
|
||||
* 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.
|
||||
*
|
||||
* ecmcFFTWrap.cpp
|
||||
*
|
||||
* Created on: Mar 22, 2020
|
||||
* Author: anderssandstrom
|
||||
*
|
||||
\*************************************************************************/
|
||||
|
||||
// Needed to get headers in ecmc right...
|
||||
#define ECMC_IS_PLUGIN
|
||||
|
||||
#include <vector>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include "ecmcSocketCANWrap.h"
|
||||
#include "ecmcSocketCAN.h"
|
||||
#include "ecmcSocketCANDefs.h"
|
||||
#include <epicsTypes.h>
|
||||
#include <epicsTime.h>
|
||||
#include <epicsThread.h>
|
||||
#include <epicsString.h>
|
||||
#include <epicsTimer.h>
|
||||
#include <epicsMutex.h>
|
||||
#include <epicsExport.h>
|
||||
#include <epicsEvent.h>
|
||||
|
||||
#include <iocsh.h>
|
||||
|
||||
|
||||
#define ECMC_PLUGIN_MAX_PORTNAME_CHARS 64
|
||||
#define ECMC_PLUGIN_PORTNAME_PREFIX "PLUGIN.CAN"
|
||||
|
||||
static ecmcSocketCAN* can = NULL;
|
||||
static char portNameBuffer[ECMC_PLUGIN_MAX_PORTNAME_CHARS];
|
||||
|
||||
int createSocketCAN(char* configStr, int exeSampleTimeMs) {
|
||||
|
||||
// create new ecmcFFT object
|
||||
|
||||
// create asynport name for new object ()
|
||||
memset(portNameBuffer, 0, ECMC_PLUGIN_MAX_PORTNAME_CHARS);
|
||||
snprintf (portNameBuffer, ECMC_PLUGIN_MAX_PORTNAME_CHARS,
|
||||
ECMC_PLUGIN_PORTNAME_PREFIX);
|
||||
try {
|
||||
can = new ecmcSocketCAN(configStr, portNameBuffer, exeSampleTimeMs);
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
if(can) {
|
||||
delete can;
|
||||
}
|
||||
printf("Exception: %s. Plugin will unload.\n",e.what());
|
||||
return ECMC_PLUGIN_SOCKETCAN_ERROR_CODE;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int connectSocketCAN() {
|
||||
if(can){
|
||||
try {
|
||||
can->connectExternal();
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
printf("Exception: %s.\n",e.what());
|
||||
return ECMC_PLUGIN_SOCKETCAN_ERROR_CODE;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return ECMC_PLUGIN_SOCKETCAN_ERROR_CODE;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int getSocketCANConnectd() {
|
||||
if(can){
|
||||
try {
|
||||
return can->getConnected();
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
printf("Exception: %s.\n",e.what());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int getlastWritesError() {
|
||||
if(can){
|
||||
try {
|
||||
return can->getlastWritesError();
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
printf("Exception: %s.\n",e.what());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int execute() {
|
||||
if(can){
|
||||
can->execute();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int addWriteSocketCAN( double canId,
|
||||
double len,
|
||||
double data0,
|
||||
double data1,
|
||||
double data2,
|
||||
double data3,
|
||||
double data4,
|
||||
double data5,
|
||||
double data6,
|
||||
double data7) {
|
||||
if(can){
|
||||
try {
|
||||
return can->addWriteCAN((uint32_t) canId,
|
||||
(uint8_t) len,
|
||||
(uint8_t) data0,
|
||||
(uint8_t) data1,
|
||||
(uint8_t) data2,
|
||||
(uint8_t) data3,
|
||||
(uint8_t) data4,
|
||||
(uint8_t) data5,
|
||||
(uint8_t) data6,
|
||||
(uint8_t) data7);
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
printf("Exception: %s.\n",e.what());
|
||||
return ECMC_PLUGIN_SOCKETCAN_ERROR_CODE;
|
||||
}
|
||||
}
|
||||
return ECMC_PLUGIN_SOCKETCAN_ERROR_CODE;
|
||||
}
|
||||
|
||||
void deleteSocketCAN() {
|
||||
if(can) {
|
||||
delete (can);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* EPICS iocsh shell command: ecmcCANOpenAddMaster
|
||||
*/
|
||||
|
||||
void ecmcCANOpenAddMasterPrintHelp() {
|
||||
printf("\n");
|
||||
printf(" Use ecmcCANOpenAddMaster(<name>, <node id>,....)\n");
|
||||
printf(" <name> : Name of master device.\n");
|
||||
printf(" <node id> : CANOpen node id of master.\n");
|
||||
printf(" <LSS sample time ms> : Sample time for LSS.\n");
|
||||
printf(" <Sync sample time ms> : Sample time for SYNC.\n");
|
||||
printf(" <NMT Heartbeat sample time ms> : Sample time for NMT Heartbeat.\n");
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
int ecmcCANOpenAddMaster(const char* name,
|
||||
int nodeId,
|
||||
int lssSampleTimeMs,
|
||||
int syncSampleTimeMs,
|
||||
int heartSampleTimeMs) {
|
||||
|
||||
if(!name) {
|
||||
printf("Error: name.\n");
|
||||
ecmcCANOpenAddMasterPrintHelp();
|
||||
return asynError;
|
||||
}
|
||||
|
||||
if(strcmp(name,"-h") == 0 || strcmp(name,"--help") == 0 ) {
|
||||
ecmcCANOpenAddMasterPrintHelp();
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
if(!can) {
|
||||
printf("Plugin not initialized/loaded.\n");
|
||||
return asynError;
|
||||
}
|
||||
|
||||
try {
|
||||
can->addMaster((uint32_t)nodeId,
|
||||
name,
|
||||
lssSampleTimeMs,
|
||||
syncSampleTimeMs,
|
||||
heartSampleTimeMs);
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
printf("Exception: %s. Add master failed.\n",e.what());
|
||||
return asynError;
|
||||
}
|
||||
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
static const iocshArg initArg0_0 =
|
||||
{ "Name", iocshArgString };
|
||||
static const iocshArg initArg1_0 =
|
||||
{ "Node Id", iocshArgInt };
|
||||
static const iocshArg initArg2_0 =
|
||||
{ "LSS sample time ms", iocshArgInt };
|
||||
static const iocshArg initArg3_0 =
|
||||
{ "Sync sample time ms", iocshArgInt };
|
||||
static const iocshArg initArg4_0 =
|
||||
{ "NMT Heart sample time ms", iocshArgInt };
|
||||
|
||||
static const iocshArg *const initArgs_0[] = { &initArg0_0,
|
||||
&initArg1_0,
|
||||
&initArg2_0,
|
||||
&initArg3_0,
|
||||
&initArg4_0};
|
||||
|
||||
static const iocshFuncDef initFuncDef_0 = { "ecmcCANOpenAddMaster", 5, initArgs_0 };
|
||||
static void initCallFunc_0(const iocshArgBuf *args) {
|
||||
ecmcCANOpenAddMaster(args[0].sval,
|
||||
args[1].ival,
|
||||
args[2].ival,
|
||||
args[3].ival,
|
||||
args[4].ival);
|
||||
}
|
||||
|
||||
/**
|
||||
* EPICS iocsh shell command: ecmcCANOpenAddDevice
|
||||
*/
|
||||
|
||||
void ecmcCANOpenAddDevicePrintHelp() {
|
||||
printf("\n");
|
||||
printf(" Use ecmcCANOpenAddDevice(<name>, <node id>)\n");
|
||||
printf(" <name> : Name of device.\n");
|
||||
printf(" <node id> : CANOpen node id of device.\n");
|
||||
printf(" <NMT Heartbeat timeout ms> : Timeout for NMT Heartbeat.\n");
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
int ecmcCANOpenAddDevice(const char* name, int nodeId,int heartTimeOutMs) {
|
||||
if(!name) {
|
||||
printf("Error: name.\n");
|
||||
ecmcCANOpenAddDevicePrintHelp();
|
||||
return asynError;
|
||||
}
|
||||
|
||||
if(strcmp(name,"-h") == 0 || strcmp(name,"--help") == 0 ) {
|
||||
ecmcCANOpenAddDevicePrintHelp();
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
if(!can) {
|
||||
printf("Plugin not initialized/loaded.\n");
|
||||
return asynError;
|
||||
}
|
||||
|
||||
if(heartTimeOutMs < 0) {
|
||||
printf("Invalid NMT heartbeat timeout.\n");
|
||||
return asynError;
|
||||
}
|
||||
|
||||
try {
|
||||
can->addDevice((uint32_t)nodeId,name,heartTimeOutMs);
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
printf("Exception: %s. Add device failed.\n",e.what());
|
||||
return asynError;
|
||||
}
|
||||
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
static const iocshArg initArg0_1 =
|
||||
{ "Name", iocshArgString };
|
||||
static const iocshArg initArg1_1 =
|
||||
{ "Node Id", iocshArgInt };
|
||||
static const iocshArg initArg2_1 =
|
||||
{ "NMT Heart timeout ms", iocshArgInt };
|
||||
|
||||
static const iocshArg *const initArgs_1[] = { &initArg0_1,
|
||||
&initArg1_1,
|
||||
&initArg2_1};
|
||||
|
||||
static const iocshFuncDef initFuncDef_1 = { "ecmcCANOpenAddDevice", 3, initArgs_1 };
|
||||
static void initCallFunc_1(const iocshArgBuf *args) {
|
||||
ecmcCANOpenAddDevice(args[0].sval, args[1].ival, args[2].ival);
|
||||
}
|
||||
|
||||
/**
|
||||
* EPICS iocsh shell command: ecmcCANOpenAddSDO
|
||||
*/
|
||||
|
||||
void ecmcCANOpenAddSDOPrintHelp() {
|
||||
printf("\n");
|
||||
printf(" Use ecmcCANOpenAddSDO(<name>, <node id>,.....)\n");
|
||||
printf(" <name> : Name of master device.\n");
|
||||
printf(" <node id> : CANOpen node id of device/master.\n");
|
||||
printf(" <cob id tx> : CANOpen cob id of Tx of slave SDO.\n");
|
||||
printf(" <cob id rx> : CANOpen cob id of Rx of slave SDO.\n");
|
||||
printf(" <dir> : Direction 1=write and 2=read.\n");
|
||||
printf(" <ODIndex> : OD index of SDO.\n");
|
||||
printf(" <ODSubIndex> : OD sub index of SDO.\n");
|
||||
printf(" <ODSize> : OS Size.\n");
|
||||
printf(" <readSampleTimeMs>: Sample time for read in ms (write is always on demand).\n");
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
int ecmcCANOpenAddSDO(const char* name,
|
||||
int nodeId,
|
||||
int cobIdTx,
|
||||
int cobIdRx,
|
||||
int dir,
|
||||
int ODIndex,
|
||||
int ODSubIndex,
|
||||
int ODSize,
|
||||
int readSampleTimeMs) {
|
||||
if(!name) {
|
||||
printf("Error: name.\n");
|
||||
ecmcCANOpenAddSDOPrintHelp();
|
||||
return asynError;
|
||||
}
|
||||
|
||||
if(strcmp(name,"-h") == 0 || strcmp(name,"--help") == 0 ) {
|
||||
ecmcCANOpenAddSDOPrintHelp();
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
if(cobIdRx < 0) {
|
||||
printf("Error: invalid cobIdRx.\n");
|
||||
ecmcCANOpenAddSDOPrintHelp();
|
||||
return asynError;
|
||||
}
|
||||
|
||||
if(cobIdTx < 0) {
|
||||
printf("Error: invalid cobIdTx.\n");
|
||||
ecmcCANOpenAddSDOPrintHelp();
|
||||
return asynError;
|
||||
}
|
||||
|
||||
if(dir > 2 || dir <= 0) {
|
||||
printf("Error: invalid dir.\n");
|
||||
ecmcCANOpenAddSDOPrintHelp();
|
||||
return asynError;
|
||||
}
|
||||
|
||||
if(ODIndex < 0) {
|
||||
printf("Error: invalid ODIndex.\n");
|
||||
ecmcCANOpenAddSDOPrintHelp();
|
||||
return asynError;
|
||||
}
|
||||
|
||||
if(ODSubIndex < 0) {
|
||||
printf("Error: invalid ODSubIndex.\n");
|
||||
ecmcCANOpenAddSDOPrintHelp();
|
||||
return asynError;
|
||||
}
|
||||
|
||||
if(ODSize < 0) {
|
||||
printf("Error: invalid ODSize.\n");
|
||||
ecmcCANOpenAddSDOPrintHelp();
|
||||
return asynError;
|
||||
}
|
||||
|
||||
if(readSampleTimeMs < 0) {
|
||||
printf("Error: invalid readSampleTimeMs.\n");
|
||||
ecmcCANOpenAddSDOPrintHelp();
|
||||
return asynError;
|
||||
}
|
||||
|
||||
ecmc_can_direction tempDir = DIR_READ;
|
||||
if(dir == 1) {
|
||||
tempDir = DIR_WRITE;
|
||||
}
|
||||
|
||||
try {
|
||||
can->addSDO((uint32_t)nodeId,
|
||||
cobIdTx,
|
||||
cobIdRx,
|
||||
tempDir,
|
||||
ODIndex,
|
||||
ODSubIndex,
|
||||
ODSize,
|
||||
readSampleTimeMs,
|
||||
name);
|
||||
|
||||
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
printf("Exception: %s. Add PDO failed.\n",e.what());
|
||||
return asynError;
|
||||
}
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
static const iocshArg initArg0_2 =
|
||||
{ "Name", iocshArgString };
|
||||
static const iocshArg initArg1_2 =
|
||||
{ "Node Id", iocshArgInt };
|
||||
static const iocshArg initArg2_2 =
|
||||
{ "COB id TX", iocshArgInt };
|
||||
static const iocshArg initArg3_2 =
|
||||
{ "COB id RX", iocshArgInt };
|
||||
static const iocshArg initArg4_2 =
|
||||
{ "Direction", iocshArgInt };
|
||||
static const iocshArg initArg5_2 =
|
||||
{ "OD Index", iocshArgInt };
|
||||
static const iocshArg initArg6_2 =
|
||||
{ "OD sub index", iocshArgInt };
|
||||
static const iocshArg initArg7_2 =
|
||||
{ "OD size", iocshArgInt };
|
||||
static const iocshArg initArg8_2 =
|
||||
{ "Read sample time ms", iocshArgInt };
|
||||
|
||||
static const iocshArg *const initArgs_2[] = { &initArg0_2,
|
||||
&initArg1_2,
|
||||
&initArg2_2,
|
||||
&initArg3_2,
|
||||
&initArg4_2,
|
||||
&initArg5_2,
|
||||
&initArg6_2,
|
||||
&initArg7_2,
|
||||
&initArg8_2};
|
||||
|
||||
static const iocshFuncDef initFuncDef_2 = { "ecmcCANOpenAddSDO", 9, initArgs_2 };
|
||||
static void initCallFunc_2(const iocshArgBuf *args) {
|
||||
ecmcCANOpenAddSDO(args[0].sval,
|
||||
args[1].ival,
|
||||
args[2].ival,
|
||||
args[3].ival,
|
||||
args[4].ival,
|
||||
args[5].ival,
|
||||
args[6].ival,
|
||||
args[7].ival,
|
||||
args[8].ival);
|
||||
}
|
||||
|
||||
/**
|
||||
* EPICS iocsh shell command: ecmcCANOpenAddPDO
|
||||
*/
|
||||
void ecmcCANOpenAddPDOPrintHelp() {
|
||||
printf("\n");
|
||||
printf(" Use \"ecmcCANOpenAddPDO(<name>, <node id>\n");
|
||||
printf(" <name> : Name of master device.\n");
|
||||
printf(" <node id> : CANOpen node id of device/master.\n");
|
||||
printf(" <cob id> : CANOpen cob id of PDO.\n");
|
||||
printf(" <dir> : Direction 1=write and 2=read.\n");
|
||||
printf(" <ODSize> : Size of PDO (max 8 bytes).\n");
|
||||
printf(" <readTimeoutMs> : Readtimeout in ms.\n");
|
||||
printf(" <writeCycleMs> : Cycle time for write (if <= 0 then only write on change).\n");
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
int ecmcCANOpenAddPDO(const char* name,
|
||||
int nodeId,
|
||||
int cobId,
|
||||
int dir,
|
||||
int ODSize,
|
||||
int readTimeoutMs,
|
||||
int writeCycleMs) {
|
||||
if(!name) {
|
||||
printf("Error: name.\n");
|
||||
ecmcCANOpenAddPDOPrintHelp();
|
||||
return asynError;
|
||||
}
|
||||
|
||||
if(strcmp(name,"-h") == 0 || strcmp(name,"--help") == 0 ) {
|
||||
ecmcCANOpenAddPDOPrintHelp();
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
if(dir > 2 || dir <= 0) {
|
||||
printf("Error: invalid dir.\n");
|
||||
ecmcCANOpenAddPDOPrintHelp();
|
||||
return asynError;
|
||||
}
|
||||
|
||||
if(ODSize < 0) {
|
||||
printf("Error: invalid ODSize.\n");
|
||||
ecmcCANOpenAddPDOPrintHelp();
|
||||
return asynError;
|
||||
}
|
||||
|
||||
if(readTimeoutMs < 0) {
|
||||
printf("Error: invalid readTimeoutMs.\n");
|
||||
ecmcCANOpenAddPDOPrintHelp();
|
||||
return asynError;
|
||||
}
|
||||
|
||||
if(writeCycleMs < 0) {
|
||||
printf("Error: invalid writeCycleMs.\n");
|
||||
ecmcCANOpenAddPDOPrintHelp();
|
||||
return asynError;
|
||||
}
|
||||
|
||||
ecmc_can_direction tempDir = DIR_READ;
|
||||
if(dir == 1) {
|
||||
tempDir = DIR_WRITE;
|
||||
}
|
||||
|
||||
try {
|
||||
can->addPDO((uint32_t)nodeId,
|
||||
cobId,
|
||||
tempDir,
|
||||
ODSize,
|
||||
readTimeoutMs,
|
||||
writeCycleMs,name);
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
printf("Exception: %s. Add PDO failed.\n",e.what());
|
||||
return asynError;
|
||||
}
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
static const iocshArg initArg0_3 =
|
||||
{ "Name", iocshArgString };
|
||||
static const iocshArg initArg1_3 =
|
||||
{ "Node Id", iocshArgInt };
|
||||
static const iocshArg initArg2_3 =
|
||||
{ "COB Id", iocshArgInt };
|
||||
static const iocshArg initArg3_3 =
|
||||
{ "Direction", iocshArgInt };
|
||||
static const iocshArg initArg4_3 =
|
||||
{ "ODSize", iocshArgInt };
|
||||
static const iocshArg initArg5_3 =
|
||||
{ "Read Timeout ms", iocshArgInt };
|
||||
static const iocshArg initArg6_3 =
|
||||
{ "Write cycle ms", iocshArgInt };
|
||||
|
||||
static const iocshArg *const initArgs_3[] = { &initArg0_3,
|
||||
&initArg1_3,
|
||||
&initArg2_3,
|
||||
&initArg3_3,
|
||||
&initArg4_3,
|
||||
&initArg5_3,
|
||||
&initArg6_3
|
||||
};
|
||||
|
||||
static const iocshFuncDef initFuncDef_3 = { "ecmcCANOpenAddPDO", 7, initArgs_3 };
|
||||
static void initCallFunc_3(const iocshArgBuf *args) {
|
||||
ecmcCANOpenAddPDO(args[0].sval,
|
||||
args[1].ival,
|
||||
args[2].ival,
|
||||
args[3].ival,
|
||||
args[4].ival,
|
||||
args[5].ival,
|
||||
args[6].ival);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register all functions
|
||||
*/
|
||||
void ecmcCANPluginDriverRegister(void) {
|
||||
iocshRegister(&initFuncDef_0, initCallFunc_0); // ecmcCANOpenAddMaster
|
||||
iocshRegister(&initFuncDef_1, initCallFunc_1); // ecmcCANOpenAddDevice
|
||||
iocshRegister(&initFuncDef_2, initCallFunc_2); // ecmcCANOpenAddSDO
|
||||
iocshRegister(&initFuncDef_3, initCallFunc_3); // ecmcCANOpenAddPDO
|
||||
}
|
||||
|
||||
epicsExportRegistrar(ecmcCANPluginDriverRegister);
|
||||
@@ -7,7 +7,6 @@
|
||||
*
|
||||
* Created on: Mar 22, 2020
|
||||
* Author: anderssandstrom
|
||||
* Credits to https://github.com/sgreg/dynamic-loading
|
||||
*
|
||||
\*************************************************************************/
|
||||
|
||||
@@ -32,6 +31,9 @@ void f_worker_write(void *obj) {
|
||||
}
|
||||
|
||||
/** ecmc ecmcSocketCANWriteBuffer class
|
||||
* Implements writing of can messages to a socket.
|
||||
* Two buffers are used. While a thread writes to socket from one of teh buffers,
|
||||
* data can still be added to the other buffer. Then teh buffers are switched.
|
||||
*/
|
||||
ecmcSocketCANWriteBuffer::ecmcSocketCANWriteBuffer(int socketId, int cfgDbgMode) {
|
||||
memset(&buffer1_.frames,0,sizeof(struct can_frame)*ECMC_CAN_MAX_WRITE_CMDS);
|
||||
@@ -43,9 +45,8 @@ ecmcSocketCANWriteBuffer::ecmcSocketCANWriteBuffer(int socketId, int cfgDbgMode)
|
||||
destructs_ = 0;
|
||||
bufferSwitchMutex_ = epicsMutexCreate();
|
||||
lastWriteSumError_ = 0;
|
||||
|
||||
writePauseTime_.tv_sec = 0;
|
||||
writePauseTime_.tv_nsec = 2e6; // 1ms
|
||||
writePauseTime_.tv_nsec = 2e6; // 2ms
|
||||
buffer1_.frameCounter = 0;
|
||||
buffer2_.frameCounter = 0;
|
||||
|
||||
@@ -130,8 +131,10 @@ int ecmcSocketCANWriteBuffer::addWriteCAN( uint32_t canId,
|
||||
return addWriteCAN(&frame);
|
||||
}
|
||||
|
||||
int ecmcSocketCANWriteBuffer::getlastWritesError() {
|
||||
return lastWriteSumError_;
|
||||
int ecmcSocketCANWriteBuffer::getlastWritesErrorAndReset() {
|
||||
int tempError = lastWriteSumError_;
|
||||
lastWriteSumError_ = 0;
|
||||
return tempError;
|
||||
}
|
||||
|
||||
int ecmcSocketCANWriteBuffer::addToBuffer(can_frame *frame) {
|
||||
@@ -145,23 +148,6 @@ int ecmcSocketCANWriteBuffer::addToBuffer(can_frame *frame) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
//void ecmcSocketCANWriteBuffer::addToBuffer1(can_frame *frame) {
|
||||
// printf("addToBuffer1\n");
|
||||
// epicsMutexLock(bufferMutex1_);
|
||||
// buffer1_.frame[buffer1_.frameCounter] = *frame;
|
||||
// buffer1_.frameCounter++;
|
||||
// epicsMutexUnlock(bufferMutex1_);
|
||||
//}
|
||||
//
|
||||
//void ecmcSocketCANWriteBuffer::addToBuffer2(can_frame *frame) {
|
||||
// printf("addToBuffer2\n");
|
||||
// epicsMutexLock(bufferMutex2_);
|
||||
// buffer2_.frame[buffer2_.frameCounter] = *frame;
|
||||
// buffer2_.frameCounter++;
|
||||
// epicsMutexUnlock(bufferMutex2_);
|
||||
//}
|
||||
//
|
||||
|
||||
int ecmcSocketCANWriteBuffer::writeBuffer() {
|
||||
|
||||
int errorCode = 0;
|
||||
@@ -179,43 +165,6 @@ int ecmcSocketCANWriteBuffer::writeBuffer() {
|
||||
return lastWriteSumError_;
|
||||
}
|
||||
|
||||
//void ecmcSocketCANWriteBuffer::writeBuffer1() {
|
||||
// //printf("writeBuffer1\n");
|
||||
// int errorCode = 0;
|
||||
// epicsMutexLock(bufferMutex1_);
|
||||
// if(buffer1_.frameCounter==0) {
|
||||
// return;
|
||||
// }
|
||||
// printf("writeBuffer1\n");
|
||||
// for(int i=0; i<buffer1_.frameCounter;i++) {
|
||||
// errorCode = writeCAN(&buffer1_.frame[i]);
|
||||
// if(errorCode) {
|
||||
// lastWriteSumError_ = errorCode;
|
||||
// }
|
||||
// }
|
||||
// buffer1_.frameCounter = 0;
|
||||
// epicsMutexUnlock(bufferMutex1_);
|
||||
//}
|
||||
//
|
||||
//void ecmcSocketCANWriteBuffer::writeBuffer2() {
|
||||
//
|
||||
// int errorCode = 0;
|
||||
// epicsMutexLock(bufferMutex2_);
|
||||
// if(buffer2_.frameCounter==0) {
|
||||
// return;
|
||||
// }
|
||||
// printf("writeBuffer2\n");
|
||||
//
|
||||
// for(int i=0; i<buffer2_.frameCounter;i++) {
|
||||
// errorCode = writeCAN(&buffer2_.frame[i]);
|
||||
// if(errorCode) {
|
||||
// lastWriteSumError_ = errorCode;
|
||||
// }
|
||||
// }
|
||||
// buffer2_.frameCounter = 0;
|
||||
// epicsMutexUnlock(bufferMutex2_);
|
||||
//}
|
||||
|
||||
int ecmcSocketCANWriteBuffer::switchBuffer() {
|
||||
|
||||
// ensure safe buffer switch
|
||||
@@ -236,7 +185,14 @@ int ecmcSocketCANWriteBuffer::writeCAN(can_frame *frame){
|
||||
|
||||
// Maybe need to add the size to write here.. if struct is not full, hmm?!
|
||||
int nbytes = write(socketId_, frame, sizeof(struct can_frame));
|
||||
|
||||
if(nbytes == -1) {
|
||||
printf("ecmcSocketCAN: write() fail with error: %s (0x%x).\n", strerror(errno),errno);
|
||||
return errno;
|
||||
}
|
||||
|
||||
if (nbytes!= sizeof(struct can_frame)) {
|
||||
printf("ecmcSocketCAN: write() fail with error: Incomplete write(), not a full can frame (0x%x).\n",ECMC_CAN_ERROR_WRITE_INCOMPLETE);
|
||||
return ECMC_CAN_ERROR_WRITE_INCOMPLETE;
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ class ecmcSocketCANWriteBuffer {
|
||||
uint8_t data6,
|
||||
uint8_t data7);
|
||||
int addWriteCAN(can_frame *frame);
|
||||
int getlastWritesError();
|
||||
int getlastWritesErrorAndReset();
|
||||
|
||||
private:
|
||||
static std::string to_string(int value);
|
||||
@@ -90,7 +90,7 @@ class ecmcSocketCANWriteBuffer {
|
||||
int writeBusy_;
|
||||
int lastWriteSumError_;
|
||||
int bufferIdAddFrames_;
|
||||
timespec writePauseTime_;
|
||||
timespec writePauseTime_;
|
||||
};
|
||||
|
||||
#endif /* ECMC_SOCKETCAN_BUFFER_WRITE_H_ */
|
||||
Reference in New Issue
Block a user