Merge branch 'add_fake_library' into 'stable-1.6'
Add fake library See merge request etherlab.org/ethercat!136
This commit is contained in:
commit
419f62161b
|
|
@ -33,6 +33,7 @@ ethercat.spec
|
|||
examples/dc_user/ec_dc_user_example
|
||||
examples/user/ec_user_example
|
||||
examples/user/build/
|
||||
fake_lib/libfakeethercat.la
|
||||
lib/*.cmake
|
||||
lib/libethercat.la
|
||||
lib/libethercat.pc
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
image:
|
||||
registry.gitlab.com/etherlab.org/build-container-factory/leap-15.3:linux-syms
|
||||
registry.gitlab.com/etherlab.org/build-container-factory/leap-15.4:linux-syms
|
||||
|
||||
stages:
|
||||
- build
|
||||
|
|
@ -60,14 +60,14 @@ build:
|
|||
|
||||
script:
|
||||
- ./bootstrap
|
||||
- ./configure --with-linux-dir=/usr/src/linux-obj/$(uname -i)/default --enable-tty --with-devices=2 --enable-ccat
|
||||
- ./configure --with-linux-dir=/usr/src/linux-obj/$(uname -i)/default --enable-tty --with-devices=2 --enable-ccat --enable-fakeuserlib
|
||||
- make -j8 all modules
|
||||
- make -C lib symbol-version-check
|
||||
- make DISTCHECK_CONFIGURE_FLAGS="--with-linux-dir=/usr/src/linux-obj/$(uname -i)/default" distcheck
|
||||
- make dist
|
||||
- mkdir test_dist && cd test_dist && tar xzf ../ethercat-*.tar.gz && cd ethercat-*/
|
||||
- mkdir build && cd build
|
||||
- ../configure --with-linux-dir=/usr/src/linux-obj/$(uname -i)/default --disable-8139too --enable-tty --with-devices=2 --enable-ccat
|
||||
- ../configure --with-linux-dir=/usr/src/linux-obj/$(uname -i)/default --disable-8139too --enable-tty --with-devices=2 --enable-ccat --enable-fakeuserlib
|
||||
- make -j8 all modules
|
||||
|
||||
artifacts:
|
||||
|
|
@ -124,8 +124,11 @@ doxygen:
|
|||
# It must be named 'pdf' and must produce a 'pdf' directory as an artifact.
|
||||
# All PDF files in this directory will be collected.
|
||||
pdf:
|
||||
needs:
|
||||
- job: commands
|
||||
artifacts: true
|
||||
stage: doc
|
||||
image: registry.gitlab.com/etherlab.org/build-container-factory/leap-15.3:texlive
|
||||
image: registry.gitlab.com/etherlab.org/build-container-factory/leap-15.4:texlive
|
||||
script:
|
||||
- cd documentation
|
||||
- git show -s --format="\def\revision{%h}\def\gitversion{%(describe)}\def\gittag{%(describe:abbrev=0)}\def\gitauthor{%an}\def\isodate#1-#2-#3x{\day=#3 \month=#2 \year=#1}\isodate %csx" HEAD > git.tex
|
||||
|
|
|
|||
2947
Doxyfile.in
2947
Doxyfile.in
File diff suppressed because it is too large
Load Diff
|
|
@ -47,6 +47,11 @@ SUBDIRS += \
|
|||
examples
|
||||
endif
|
||||
|
||||
if ENABLE_FAKEUSERLIB
|
||||
SUBDIRS += \
|
||||
fake_lib
|
||||
endif
|
||||
|
||||
if ENABLE_TTY
|
||||
SUBDIRS += tty
|
||||
endif
|
||||
|
|
|
|||
4
NEWS
4
NEWS
|
|
@ -4,6 +4,10 @@ vim: spelllang=en spell tw=78
|
|||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
Changes in 1.6.1:
|
||||
|
||||
* Added libfakeethercat to simulate Process Data of EtherCAT Slaves.
|
||||
|
||||
Changes in 1.6.0:
|
||||
|
||||
* Added all native Ethernet drivers for kernels 5.14 and 6.1.
|
||||
|
|
|
|||
15
README.md
15
README.md
|
|
@ -15,7 +15,7 @@ Contents:
|
|||
This is an open-source EtherCAT master implementation for Linux 2.6 or newer.
|
||||
|
||||
See the [features file](FEATURES.md) for a list of features. For more
|
||||
information, see http://etherlab.org/en/ethercat.
|
||||
information, see https://etherlab.org/ethercat.
|
||||
|
||||
or contact
|
||||
|
||||
|
|
@ -54,7 +54,7 @@ make doc
|
|||
```
|
||||
|
||||
An up-to-date Doxygen output can be found on
|
||||
[docs.etherlab.org](https://docs.etherlab.org/ethercat/1.5/doxygen/index.html).
|
||||
[docs.etherlab.org](https://docs.etherlab.org/ethercat/1.6/doxygen/index.html).
|
||||
|
||||
# Requirements
|
||||
|
||||
|
|
@ -65,13 +65,20 @@ the EtherCAT master.
|
|||
|
||||
## Hardware requirements
|
||||
|
||||
A table of supported hardware can be found at
|
||||
http://etherlab.org/en/ethercat/hardware.php.
|
||||
A table of supported hardware can be found at:
|
||||
https://docs.etherlab.org/ethercat/1.6/doxygen/devicedrivers.html
|
||||
|
||||
# Building and installing
|
||||
|
||||
See the [install file](INSTALL.md).
|
||||
|
||||
# Dry-run and Field Simulation
|
||||
|
||||
A limited set of the userspace API is available in `libfakeethercat`,
|
||||
a library which can be used to run an userspace application
|
||||
without an EtherCAT master or with emulated EtherCAT slaves.
|
||||
Please find some details [here](fake_lib/README.md).
|
||||
|
||||
# Realtime and Tuning
|
||||
|
||||
Realtime patches for the Linux kernel are supported, but not required. The
|
||||
|
|
|
|||
34
configure.ac
34
configure.ac
|
|
@ -31,7 +31,7 @@ AC_PREREQ(2.59)
|
|||
# - Update version number below
|
||||
# - make dist-bzip2
|
||||
#
|
||||
AC_INIT([ethercat],[1.6.0],[fp@igh.de])
|
||||
AC_INIT([ethercat],[1.6.1],[fp@igh.de])
|
||||
AC_CONFIG_AUX_DIR([autoconf])
|
||||
AM_INIT_AUTOMAKE([-Wall -Wno-override -Werror dist-bzip2 subdir-objects foreign])
|
||||
m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
|
||||
|
|
@ -1168,6 +1168,37 @@ fi
|
|||
|
||||
AM_CONDITIONAL(ENABLE_USERLIB, test "x$userlib" = "x1")
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Fake Userspace library generation
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
AC_MSG_CHECKING([whether to build the fake userspace library])
|
||||
|
||||
AC_ARG_ENABLE([fakeuserlib],
|
||||
AS_HELP_STRING([--enable-fakeuserlib],
|
||||
[Generation of the userspace library (default: no)]),
|
||||
[
|
||||
case "${enableval}" in
|
||||
yes) fakeuserlib=1
|
||||
;;
|
||||
no) fakeuserlib=0
|
||||
;;
|
||||
*) AC_MSG_ERROR([Invalid value for --enable-fakeuserlib])
|
||||
;;
|
||||
esac
|
||||
],
|
||||
[fakeuserlib=0]
|
||||
)
|
||||
|
||||
if test "x${fakeuserlib}" = "x1"; then
|
||||
AC_MSG_RESULT([yes])
|
||||
PKG_CHECK_MODULES([RTIPC], [librtipc])
|
||||
else
|
||||
AC_MSG_RESULT([no])
|
||||
fi
|
||||
|
||||
AM_CONDITIONAL(ENABLE_FAKEUSERLIB, test "x$fakeuserlib" = "x1")
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# TTY driver
|
||||
#-----------------------------------------------------------------------------
|
||||
|
|
@ -1389,6 +1420,7 @@ AC_CONFIG_FILES([
|
|||
examples/user/Makefile
|
||||
examples/xenomai/Makefile
|
||||
examples/xenomai_posix/Makefile
|
||||
fake_lib/Makefile
|
||||
include/Makefile
|
||||
lib/Makefile
|
||||
lib/libethercat.pc
|
||||
|
|
|
|||
|
|
@ -351,7 +351,7 @@ Public License (GPL \cite{gpl})\index{GPL}, version 2. Other developers, that
|
|||
want to use EtherCAT with Linux systems, are invited to use the master code or
|
||||
even participate on development.
|
||||
|
||||
To allow static linking of userspace application against the master's
|
||||
To allow dynamic linking of userspace application against the master's
|
||||
application interface (see \autoref{chap:api}), the userspace library (see
|
||||
\autoref{sec:userlib}) is licensed under the terms and conditions of the GNU
|
||||
Lesser General Public License (LGPL \cite{lgpl})\index{LGPL}, version 2.1.
|
||||
|
|
@ -962,8 +962,8 @@ be found.
|
|||
The application interface of the EtherCAT master is defined in the header file
|
||||
\textit{include/ecrt.h} (acronym for ``EtherCAT Real-Time'') which is listed
|
||||
in this section. The calling conventions of all methods are documented in the
|
||||
comments of this header. There is also a Doxygen-generated online version at
|
||||
\url{https://docs.etherlab.org}.
|
||||
comments of this header. There is also a Doxygen-generated \cite{doxygen}
|
||||
online version at \url{https://docs.etherlab.org}.
|
||||
|
||||
\lstinputlisting[
|
||||
basicstyle=\ttfamily\scriptsize,
|
||||
|
|
@ -2709,6 +2709,10 @@ installation prefix as \textit{libethercat.a} (for static linking),
|
|||
\textit{libethercat.la} (for the use with \textit{libtool}) and
|
||||
\textit{libethercat.so} (for dynamic linking).
|
||||
|
||||
For running an application without actual EtherCAT hardware or for simulation
|
||||
purposes, there is a special library called \textit{libfakeethercat} (see
|
||||
\autoref{sec:fakelib}).
|
||||
|
||||
\subsection{Using the Library}
|
||||
|
||||
The application interface header \textit{ecrt.h} (see \autoref{sec:ecrt}) can
|
||||
|
|
@ -2750,6 +2754,9 @@ gcc -static ectest.c -o ectest -I/opt/etherlab/include \
|
|||
/opt/etherlab/lib/libethercat.a
|
||||
\end{lstlisting}
|
||||
|
||||
Please keep in mind, that your application has to be licensed under GPLv2
|
||||
then, because the LGPL does only allow dynamic linking.
|
||||
|
||||
\subsection{Implementation}
|
||||
\label{sec:userimp}
|
||||
|
||||
|
|
@ -2836,6 +2843,33 @@ The test results show, that for this configuration, the userspace API causes
|
|||
about \unit{1}{\micro\second} additional delay for each function, compared to
|
||||
the kernel API.
|
||||
|
||||
\subsection{Simulation / Fake Library}
|
||||
\label{sec:fakelib}
|
||||
|
||||
Sometimes is is handy to run your EtherCAT realtime application without an
|
||||
actual EtherCAT network connected, for example for test purposes.
|
||||
Though it is possible to spin up an EtherCAT master and to connect it to a
|
||||
loopback device, this step is not always wanted.
|
||||
|
||||
The EtherCAT master (since version 1.6.1) comes with a library
|
||||
\textit{libfakeethercat} that comes with a reasonable subset of the EtherCAT
|
||||
application interface (see \autoref{chap:api}).
|
||||
|
||||
The \texttt{ecrt} method implementation in the fake library will just accept
|
||||
your input and behave as if everything would be fine. Without further steps,
|
||||
the process data will be all-zero then.
|
||||
|
||||
As a special feature, the \textit{libfakeethercat} will create RtIPC
|
||||
\cite{rtipc} endpoints for registered PDO entries to enable a simulation
|
||||
interface. Another application that either uses RtIPC directly or another
|
||||
(inverted) instance of \textit{libfakeethercat} will then connect to these
|
||||
endpoints and thus create the possibility to provide simulated values to your
|
||||
pristine application.
|
||||
|
||||
The fake library functions an usage is documented in Doxygen~\cite{doxygen}
|
||||
and the most recent version can be found online:
|
||||
\url{https://docs.etherlab.org/ethercat/1.6/doxygen/libfakeethercat.html}
|
||||
|
||||
%------------------------------------------------------------------------------
|
||||
|
||||
\section{RTDM Interface}
|
||||
|
|
@ -3547,6 +3581,9 @@ Applications.
|
|||
\url{http://svn.gna.org/svn/xenomai/tags/v2.4.0/doc/nodist/pdf/RTDM-and-Applications.pdf},
|
||||
2013.
|
||||
|
||||
\bibitem{rtipc} Real-Time Inter-Process-Communication library. Part of the
|
||||
EtherLab toolkit. \url{https://gitlab.com/etherlab.org/rtipc}, 2024.
|
||||
|
||||
\end{thebibliography}
|
||||
|
||||
\printnomenclature
|
||||
|
|
|
|||
|
|
@ -3440,7 +3440,7 @@ Il y a plusieurs mani\`eres d'obtenir le logiciel du ma\^itre:
|
|||
|
||||
\item Une version officielle (par exemple \masterversion) peut \^etre
|
||||
t\'el\'echarg\'ee depuis le site web du
|
||||
ma\^itre\footnote{\url{http://etherlab.org/en/ethercat/index.php}}
|
||||
ma\^itre\footnote{\url{https://etherlab.org/ethercat}}
|
||||
dans le projet EtherLab~\cite{etherlab} sous forme d'archive tar.
|
||||
|
||||
\item La r\'evision de d\'eveloppement la plus r\'ecente (mais aussi
|
||||
|
|
@ -3793,7 +3793,7 @@ KERNEL=="EtherCAT[0-9]*", MODE="0664", GROUP="users"
|
|||
|
||||
\bibitem{etherlab} Ingenieurgemeinschaft IgH: EtherLab -- Open Source Toolkit
|
||||
for rapid realtime code generation under Linux with Simulink/RTW and EtherCAT
|
||||
technology. \url{http://etherlab.org/en}, 2008.
|
||||
technology. \url{https://etherlab.org}, 2024.
|
||||
|
||||
\bibitem{dlspec} IEC 61158-4-12: Data-link Protocol Specification.
|
||||
International Electrotechnical Commission (IEC), 2005.
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ Version: @VERSION@
|
|||
Release: 1
|
||||
|
||||
License: GPL
|
||||
URL: http://etherlab.org/en/ethercat
|
||||
URL: https://etherlab.org/ethercat
|
||||
|
||||
Provides: @PACKAGE@
|
||||
Source: %{name}-%{version}.tar.bz2
|
||||
|
|
@ -44,7 +44,7 @@ Group: EtherLab
|
|||
%description
|
||||
This is an open-source EtherCAT master implementation for Linux 2.6. See the
|
||||
FEATURES file for a list of features. For more information, see
|
||||
http://etherlab.org/en/ethercat.
|
||||
https://etherlab.org/ethercat.
|
||||
|
||||
%kernel_module_package
|
||||
|
||||
|
|
@ -60,7 +60,7 @@ Group: EtherLab
|
|||
%description devel
|
||||
This is an open-source EtherCAT master implementation for Linux 2.6. See the
|
||||
FEATURES file for a list of features. For more information, see
|
||||
http://etherlab.org/en/ethercat.
|
||||
https://etherlab.org/ethercat.
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
#------------------------------------------------------------------------------
|
||||
#
|
||||
# Copyright (C) 2006-2024 Florian Pose, Ingenieurgemeinschaft IgH
|
||||
#
|
||||
# This file is part of the IgH EtherCAT master userspace library.
|
||||
#
|
||||
# The IgH EtherCAT master userspace library is free software; you can
|
||||
# redistribute it and/or modify it under the terms of the GNU Lesser General
|
||||
# Public License as published by the Free Software Foundation; version 2.1 of
|
||||
# the License.
|
||||
#
|
||||
# The IgH EtherCAT master userspace library 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with the IgH EtherCAT master userspace library. If not, see
|
||||
# <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
lib_LTLIBRARIES = libfakeethercat.la
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
libfakeethercat_la_SOURCES = \
|
||||
fakeethercat.cpp
|
||||
|
||||
noinst_HEADERS = \
|
||||
fakeethercat.h
|
||||
|
||||
libfakeethercat_la_CXXFLAGS = \
|
||||
-fno-strict-aliasing \
|
||||
-Wall \
|
||||
-I$(top_srcdir)/include \
|
||||
-Dethercat_EXPORTS \
|
||||
-fvisibility=hidden \
|
||||
$(RTIPC_CFLAGS)
|
||||
|
||||
libfakeethercat_la_LDFLAGS = -version-info 3:0:2 \
|
||||
$(RTIPC_LIBS) \
|
||||
-Wl,--version-script=$(srcdir)/../lib/libethercat.map \
|
||||
-fvisibility=hidden
|
||||
|
||||
libfakeethercat_la_DEPENDENCIES = ../lib/libethercat.map
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
FakeEtherCAT Library {#libfakeethercat}
|
||||
====================
|
||||
|
||||
Libfakeethercat is a userspace library which has the same API as
|
||||
the EtherCAT master interface library libethercat.
|
||||
Libfakeethercat can be used to spin up your RT application in a dry-run mode,
|
||||
without any master configured or slaves attached.
|
||||
Furthermore, it is possible to emulate EtherCAT slaves on process data level
|
||||
by running two applications back to back.
|
||||
|
||||
## Supported features
|
||||
|
||||
Currently, only a very limited subset of libethercat functionality is supported:
|
||||
|
||||
- Creating master and domain instances
|
||||
- Activating master, `send`/`receive`.
|
||||
- Processing and queuing domains
|
||||
- Configuring PDOs
|
||||
- Configuring SDOs using `ecrt_slave_config_sdo*`
|
||||
|
||||
The SDO config does not do anything,
|
||||
but when activating the master the SDO config will
|
||||
be dumped into a JSON file.
|
||||
|
||||
ecrt_master_state() and ecrt_domain_state() both return states as if
|
||||
the bus works without errors.
|
||||
So currenty, a bus error cannot be simulated.
|
||||
|
||||
## How to build
|
||||
|
||||
[RtIPC](https://gitlab.com/etherlab.org/rtipc) is needed.
|
||||
Simply pass `--enable-fakeuserlib` to your `./configure` call
|
||||
and the library will be built for you.
|
||||
|
||||
## How to set up dry run mode
|
||||
|
||||
### Redirect library loading
|
||||
|
||||
To avoid recompiling your application,
|
||||
we will use `LD_LIBRARY_PATH` to load `libfakeethercat` instead of `libethercat`.
|
||||
|
||||
```sh
|
||||
# pick a location for an empty directory
|
||||
export MY_LIB_LOCATION=$HOME/fake_lib64
|
||||
# create that directory
|
||||
mkdir -p $MY_LIB_LOCATION
|
||||
# create a symlink to libfakeethercat
|
||||
# assuming SOVERSION is 1
|
||||
# debian users, please use /usr/lib/x86_64-linux-gnu/libfakeethercat.so.1
|
||||
ln -s /usr/lib64/libfakeethercat.so.1 $MY_LIB_LOCATION/libethercat.so.1
|
||||
# use MY_LIB_LOCATION to load libraries first
|
||||
export LD_LIBRARY_PATH=$MY_LIB_LOCATION
|
||||
# check whether everything is done right
|
||||
ldd my_application | grep ethercat
|
||||
libethercat.so.1 => /home/vh/fake_lib64/libethercat.so.1 (0x00007fa5a5c59000)
|
||||
```
|
||||
|
||||
### Set up FakeEtherCAT Home
|
||||
|
||||
RtIPC needs a place to store its configuration.
|
||||
Set `FAKE_EC_HOMEDIR` environment variabe to a path to an empty directory,
|
||||
for instance `/tmp/FakeEtherCAT`.
|
||||
`FAKE_EC_NAME` can be set to a useful name of your application,
|
||||
default is `FakeEtherCAT`.
|
||||
|
||||
```sh
|
||||
export FAKE_EC_HOMEDIR=/tmp/FakeEtherCAT
|
||||
rm -rf $FAKE_EC_HOMEDIR
|
||||
mkdir -p $FAKE_EC_HOMEDIR
|
||||
```
|
||||
|
||||
### Spin up your application
|
||||
|
||||
Now it's time to simply launch your application.
|
||||
You will notice that the PDO configuration will be dumped at stderr.
|
||||
The path displayed is the path of the RtIPC variable in the following format:
|
||||
`$FAKE_EC_PREFIX/$MASTER_ID/$DOMAIN_ID/$ALIAS$POSITION/$PDO`.
|
||||
|
||||
## How to emulate EtherCAT slaves
|
||||
|
||||
Let's say you'd like to build virtual EtherCAT slaves to emulate your field.
|
||||
Libfakeethercat makes that possible with the help of RtIPC.
|
||||
|
||||
### Building a simulator
|
||||
|
||||
Your field emulator simply has to swap the sync manager direction setting
|
||||
(EC_DIR_INPUT and EC_DIR_OUTPUT) in ec_sync_info_t.
|
||||
Libfakeethercat instances use shared memory, provided by RtIPC,
|
||||
to exchange process data.
|
||||
The direction setting decides which instance writes and which reads from
|
||||
shared memory.
|
||||
|
||||
For instance, emulating a digial output works in the following way:
|
||||
Create another real time application with the same slave information
|
||||
as your control application.
|
||||
Then, replace all EC_DIR_INPUT with EC_DIR_OUTPUT and vice versa.
|
||||
Now, remember to read process data instead of writing it, and vice versa.
|
||||
So, your EL2004 digital out will be read by your simulating application
|
||||
and has PDO object 0x1600 ff. configured with Sync Manager 2 as EC_DIR_INPUT.
|
||||
|
||||
[EtherLab](https://gitlab.com/etherlab.org/etherlab) provides
|
||||
a convenient way to build an entire simulator using SIMULINK since
|
||||
Version 2.4.0.
|
||||
Run `web(etherlab_help_path('swap_io.html'), '-helpbrowser')`
|
||||
in your MATLAB shell to read more about how to set up a
|
||||
Process Data simulator.
|
||||
|
||||
### Start your application
|
||||
|
||||
First, do all the steps explained above to run your
|
||||
control application in dry run mode.
|
||||
Then, in another shell, do the same thing with your simulator,
|
||||
but do not remove the `FAKE_EC_HOMEDIR` directory and
|
||||
pick another `FAKE_EC_NAME`.
|
||||
|
||||
Carefully watch the PDO configuration on stderr and compare them.
|
||||
All paths configured as Output on the control application have to
|
||||
be configured as Input on your simulator and vice versa.
|
||||
If you use multiple domains and there is a mismatch of the domain IDs,
|
||||
set `FAKE_EC_DOMAIN_PERMUTATION` to a space-separated list of integers to
|
||||
permutate the domain IDs of one application.
|
||||
`FAKE_EC_DOMAIN_PERMUTATION="0 1"` swaps domains 0 and 1, for instance.
|
||||
|
||||
Finally, your control application needs to be restarted
|
||||
so it can find the RtIPC variables which contains the process data of the
|
||||
simulator.
|
||||
|
||||
## Environment variables
|
||||
|
||||
- FAKE_EC_DOMAIN_PERMUTATION: Permutate the domain IDs, useful to match control and simulation applications.
|
||||
- FAKE_EC_HOMEDIR: Directory for RtIPC builletin board and SDO json files
|
||||
- FAKE_EC_NAME: Will be used for naming RtIPC config and SDO json file
|
||||
- FAKE_EC_PREFIX: Prefix for RtIPC variables, useful to run multiple simulators side by side.
|
||||
|
|
@ -0,0 +1,693 @@
|
|||
/*****************************************************************************
|
||||
*
|
||||
* Copyright (C) 2024 Bjarne von Horn, Ingenieurgemeinschaft IgH
|
||||
*
|
||||
* This file is part of the IgH EtherCAT master userspace library.
|
||||
*
|
||||
* The IgH EtherCAT master userspace library is free software; you can
|
||||
* redistribute it and/or modify it under the terms of the GNU Lesser General
|
||||
* Public License as published by the Free Software Foundation; version 2.1
|
||||
* of the License.
|
||||
*
|
||||
* The IgH EtherCAT master userspace library 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with the IgH EtherCAT master userspace library. If not, see
|
||||
* <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#include "fakeethercat.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <unordered_set>
|
||||
#include <iterator>
|
||||
#include <ios>
|
||||
|
||||
static std::vector<size_t> getPermutationVector(size_t count);
|
||||
|
||||
static std::ostream &operator<<(std::ostream &os, const sdo_address &a)
|
||||
{
|
||||
os << std::setfill('0') << std::hex << std::setw(6) << a.getCombined();
|
||||
return os;
|
||||
}
|
||||
static std::ostream &operator<<(std::ostream &os, const ec_address &a)
|
||||
{
|
||||
os << std::setfill('0') << std::hex << std::setw(8) << a.getCombined();
|
||||
return os;
|
||||
}
|
||||
|
||||
static void add_spaces(std::ostream &out, int const num)
|
||||
{
|
||||
for (int i = 0; i < num; ++i)
|
||||
{
|
||||
out << ' ';
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Map, typename Func>
|
||||
static void map2Json(std::ostream &out, const Map &map, Func &&print_func, int indent = 0)
|
||||
{
|
||||
indent += 4;
|
||||
out << "{";
|
||||
bool is_first = true;
|
||||
for (const auto &kv : map)
|
||||
{
|
||||
if (is_first)
|
||||
{
|
||||
out << '\n';
|
||||
is_first = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
out << ",\n";
|
||||
}
|
||||
add_spaces(out, indent);
|
||||
out << "\"0x" << std::hex << std::setfill('0') << std::setw(2 * sizeof(typename Map::key_type));
|
||||
out << kv.first << "\": ";
|
||||
print_func(out, kv.second);
|
||||
}
|
||||
out << '\n';
|
||||
add_spaces(out, indent - 4);
|
||||
out << "}";
|
||||
}
|
||||
|
||||
size_t pdo::sizeInBytes() const
|
||||
{
|
||||
size_t ans = 0;
|
||||
for (const auto &entry : entries)
|
||||
{
|
||||
ans += entry.bit_length;
|
||||
}
|
||||
return (ans + 7) / 8;
|
||||
}
|
||||
|
||||
Offset pdo::findEntry(uint16_t idx, uint8_t subindex) const
|
||||
{
|
||||
size_t offset_bits = 0;
|
||||
for (const auto &entry : entries)
|
||||
{
|
||||
if (entry.index == idx && entry.subindex == subindex)
|
||||
{
|
||||
return Offset(offset_bits / 8, offset_bits % 8);
|
||||
}
|
||||
offset_bits += entry.bit_length;
|
||||
}
|
||||
return NotFound;
|
||||
}
|
||||
|
||||
ec_domain::ec_domain(rtipc *rtipc, const char *prefix, ec_master_t *master) : rt_group(rtipc_create_group(rtipc, 1.0)), prefix(prefix), master(master)
|
||||
{
|
||||
}
|
||||
|
||||
int ec_domain::activate(int domain_id)
|
||||
{
|
||||
std::unordered_set<uint32_t> slaves;
|
||||
|
||||
connected.resize(mapped_pdos.size());
|
||||
size_t idx = 0;
|
||||
for (const auto &pdo : mapped_pdos)
|
||||
{
|
||||
slaves.insert(pdo.slave_address.getCombined());
|
||||
void *rt_pdo = nullptr;
|
||||
char buf[512];
|
||||
const auto fmt = snprintf(buf, sizeof(buf), "%s/%d/%d/%08X/%04X", prefix, master->getId(), domain_id, pdo.slave_address.getCombined(), pdo.pdo_index);
|
||||
if (fmt < 0 || fmt >= (int)sizeof(buf))
|
||||
{
|
||||
return -ENOBUFS;
|
||||
}
|
||||
|
||||
switch (pdo.dir)
|
||||
{
|
||||
case EC_DIR_OUTPUT:
|
||||
rt_pdo = rtipc_txpdo(rt_group, buf, rtipc_uint8_T, data.data() + pdo.offset, pdo.size_bytes);
|
||||
std::cerr << "Registering " << buf << " as Output\n";
|
||||
break;
|
||||
case EC_DIR_INPUT:
|
||||
rt_pdo = rtipc_rxpdo(rt_group, buf, rtipc_uint8_T, data.data() + pdo.offset, pdo.size_bytes, connected.data() + idx);
|
||||
std::cerr << "Registering " << buf << " as Input\n";
|
||||
break;
|
||||
default:
|
||||
std::cerr << "Unknown direction " << pdo.dir << '\n';
|
||||
return -1;
|
||||
}
|
||||
if (!rt_pdo)
|
||||
{
|
||||
std::cerr << "Failed to register RtIPC PDO\n";
|
||||
return -1;
|
||||
}
|
||||
++idx;
|
||||
}
|
||||
activated_ = true;
|
||||
numSlaves = slaves.size();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ec_domain::process()
|
||||
{
|
||||
rtipc_rx(rt_group);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ec_domain::queue()
|
||||
{
|
||||
rtipc_tx(rt_group);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ssize_t ec_domain::map(ec_slave_config const &config, unsigned int syncManager,
|
||||
uint16_t pdo_index)
|
||||
{
|
||||
if (activated_)
|
||||
return -1;
|
||||
for (const auto &pdo : mapped_pdos)
|
||||
{
|
||||
if (pdo.slave_address == config.address && syncManager == pdo.syncManager && pdo_index == pdo.pdo_index)
|
||||
{
|
||||
// already mapped;
|
||||
return pdo.offset;
|
||||
}
|
||||
}
|
||||
const auto ans = data.size();
|
||||
const auto size = config.sync_managers.at(syncManager).pdos.at(pdo_index).sizeInBytes();
|
||||
mapped_pdos.emplace_back(ans, size, config.address, syncManager, pdo_index, config.sync_managers.at(syncManager).dir);
|
||||
data.resize(ans + size);
|
||||
return ans;
|
||||
}
|
||||
|
||||
uint8_t *ecrt_domain_data(
|
||||
const ec_domain_t *domain)
|
||||
{
|
||||
return domain->getData();
|
||||
}
|
||||
|
||||
int ecrt_domain_process(
|
||||
ec_domain_t *domain)
|
||||
{
|
||||
return domain->process();
|
||||
}
|
||||
int ecrt_domain_queue(
|
||||
ec_domain_t *domain)
|
||||
{
|
||||
return domain->queue();
|
||||
}
|
||||
|
||||
int ecrt_domain_state(
|
||||
const ec_domain_t *domain, /**< Domain. */
|
||||
ec_domain_state_t *state /**< Pointer to a state object to store the
|
||||
information. */
|
||||
)
|
||||
{
|
||||
state->working_counter = domain->getNumSlaves();
|
||||
state->redundancy_active = 0;
|
||||
state->wc_state = EC_WC_COMPLETE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ec_master::activate()
|
||||
{
|
||||
const auto permutate = getPermutationVector(domains.size());
|
||||
int i = 0;
|
||||
for (auto &domain : domains)
|
||||
{
|
||||
if (domain.activate(permutate[i]))
|
||||
return -1;
|
||||
++i;
|
||||
}
|
||||
{
|
||||
std::ofstream out(rt_ipc_dir + "/" + rt_ipc_name + "_slaves.json");
|
||||
if (!out.is_open())
|
||||
{
|
||||
std::cerr << "could not dump json.\n";
|
||||
return -1;
|
||||
}
|
||||
out << "{\n \"slaves\": ";
|
||||
map2Json(out, slaves, [](std::ostream &out, const ec_slave_config &slave)
|
||||
{ slave.dumpJson(out, 8); }, 4);
|
||||
out << "\n}\n";
|
||||
}
|
||||
return rtipc_prepare(rt_ipc.get());
|
||||
}
|
||||
|
||||
int ecrt_master_activate(
|
||||
ec_master_t *master /**< EtherCAT master. */
|
||||
)
|
||||
{
|
||||
try
|
||||
{
|
||||
return master->activate();
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
std::cerr << "Could not activate: " << e.what() << '\n';
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int ecrt_master_application_time(
|
||||
ec_master_t *master, /**< EtherCAT master. */
|
||||
uint64_t app_time /**< Application time. */
|
||||
)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
ec_domain_t *ecrt_master_create_domain(
|
||||
ec_master_t *master /**< EtherCAT master. */
|
||||
)
|
||||
{
|
||||
return master->createDomain();
|
||||
}
|
||||
|
||||
static const char *getPrefix()
|
||||
{
|
||||
if (const auto ans = getenv("FAKE_EC_PREFIX"))
|
||||
return ans;
|
||||
return "/FakeEtherCAT";
|
||||
}
|
||||
|
||||
ec_domain *ec_master::createDomain()
|
||||
{
|
||||
domains.emplace_back(rt_ipc.get(), getPrefix(), this);
|
||||
return &domains.back();
|
||||
}
|
||||
int ecrt_master_link_state(
|
||||
const ec_master_t *master, /**< EtherCAT master. */
|
||||
unsigned int dev_idx, /**< Index of the device (0 = main device, 1 =
|
||||
first backup device, ...). */
|
||||
ec_master_link_state_t *state /**< Structure to store the information.
|
||||
*/
|
||||
)
|
||||
{
|
||||
state->slaves_responding = master->getNoSlaves();
|
||||
state->al_states = 4;
|
||||
state->link_up = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ecrt_master_receive(
|
||||
ec_master_t *master /**< EtherCAT master. */
|
||||
)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ecrt_master_reset(
|
||||
ec_master_t *master /**< EtherCAT master. */
|
||||
)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ecrt_master_scan_progress(
|
||||
ec_master_t *master, /**< EtherCAT master */
|
||||
ec_master_scan_progress_t *progress /**< Structure that will output
|
||||
the progress information. */
|
||||
)
|
||||
{
|
||||
progress->scan_index = progress->slave_count = master->getNoSlaves();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ecrt_master_send(
|
||||
ec_master_t *master /**< EtherCAT master. */
|
||||
)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
ec_slave_config_t *ecrt_master_slave_config(
|
||||
ec_master_t *master, /**< EtherCAT master */
|
||||
uint16_t alias, /**< Slave alias. */
|
||||
uint16_t position, /**< Slave position. */
|
||||
uint32_t vendor_id, /**< Expected vendor ID. */
|
||||
uint32_t product_code /**< Expected product code. */
|
||||
)
|
||||
{
|
||||
return master->slave_config(alias, position, vendor_id, product_code);
|
||||
}
|
||||
|
||||
ec_slave_config_t *ec_master::slave_config(
|
||||
uint16_t alias, /**< Slave alias. */
|
||||
uint16_t position, /**< Slave position. */
|
||||
uint32_t vendor_id, /**< Expected vendor ID. */
|
||||
uint32_t product_code /**< Expected product code. */
|
||||
)
|
||||
{
|
||||
const ec_address address{alias, position};
|
||||
const auto it = slaves.find(address);
|
||||
if (it != slaves.end())
|
||||
{
|
||||
if (it->second.vendor_id == vendor_id && it->second.product_code == product_code)
|
||||
return &it->second;
|
||||
else
|
||||
{
|
||||
std::cerr << "Attempted to reconfigure slave (" << alias << "," << position << ")!\n";
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return &slaves.insert(std::make_pair<ec_address, ec_slave_config>(ec_address{address}, ec_slave_config{address, vendor_id, product_code})).first->second;
|
||||
}
|
||||
}
|
||||
int ecrt_master_state(
|
||||
const ec_master_t *master, /**< EtherCAT master. */
|
||||
ec_master_state_t *state /**< Structure to store the information. */
|
||||
)
|
||||
{
|
||||
state->slaves_responding = master->getNoSlaves();
|
||||
state->link_up = 1;
|
||||
state->al_states = 8;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ecrt_master_sync_monitor_queue(
|
||||
ec_master_t *master /**< EtherCAT master. */
|
||||
)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t ecrt_master_sync_monitor_process(
|
||||
const ec_master_t *master /**< EtherCAT master. */
|
||||
)
|
||||
{
|
||||
return 32;
|
||||
}
|
||||
int ecrt_master_sync_reference_clock(
|
||||
ec_master_t *master /**< EtherCAT master. */
|
||||
)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ecrt_master_sync_reference_clock_to(
|
||||
ec_master_t *master, /**< EtherCAT master. */
|
||||
uint64_t sync_time /**< Sync reference clock to this time. */
|
||||
)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ecrt_master_sync_slave_clocks(
|
||||
ec_master_t *master /**< EtherCAT master. */
|
||||
)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
void ecrt_release_master(ec_master_t *master)
|
||||
{
|
||||
delete master;
|
||||
}
|
||||
|
||||
ec_master_t *ecrt_request_master(
|
||||
unsigned int master_index /**< Index of the master to request. */
|
||||
)
|
||||
{
|
||||
return new ec_master(master_index);
|
||||
}
|
||||
|
||||
static const char *getName()
|
||||
{
|
||||
if (const auto ans = getenv("FAKE_EC_NAME"))
|
||||
{
|
||||
return ans;
|
||||
}
|
||||
return "FakeEtherCAT";
|
||||
}
|
||||
|
||||
static const char *getRtIpcDir()
|
||||
{
|
||||
if (const auto ans = getenv("FAKE_EC_HOMEDIR"))
|
||||
{
|
||||
return ans;
|
||||
}
|
||||
return "/tmp/FakeEtherCAT";
|
||||
}
|
||||
|
||||
static std::vector<size_t> getPermutationVector(size_t count)
|
||||
{
|
||||
std::vector<size_t> ans;
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
{
|
||||
ans.push_back(i);
|
||||
}
|
||||
const auto spec = getenv("FAKE_EC_DOMAIN_PERMUTATION");
|
||||
if (!spec)
|
||||
return ans;
|
||||
std::istringstream is(spec);
|
||||
std::istream_iterator<int> begin(is), end;
|
||||
std::vector<int> values(begin, end);
|
||||
if (values.size() % 2)
|
||||
{
|
||||
throw std::invalid_argument("Specify an even number of indices to permutate.\n");
|
||||
}
|
||||
for (size_t i = 0; i < values.size() / 2; ++i)
|
||||
{
|
||||
std::swap(ans.at(values[2 * i]), ans.at(values[2 * i + 1]));
|
||||
}
|
||||
return ans;
|
||||
}
|
||||
|
||||
ec_master::ec_master(int id) : rt_ipc_dir(getRtIpcDir()), rt_ipc_name(getName()), rt_ipc(rtipc_create(rt_ipc_name.c_str(), rt_ipc_dir.c_str())), id_(id)
|
||||
{
|
||||
}
|
||||
|
||||
int ecrt_slave_config_complete_sdo(
|
||||
ec_slave_config_t *sc, /**< Slave configuration. */
|
||||
uint16_t index, /**< Index of the SDO to configure. */
|
||||
const uint8_t *data, /**< Pointer to the data. */
|
||||
size_t size /**< Size of the \a data. */
|
||||
)
|
||||
{
|
||||
return ecrt_slave_config_sdo(sc, index, 0, data, size);
|
||||
}
|
||||
ec_sdo_request_t *ecrt_slave_config_create_sdo_request(
|
||||
ec_slave_config_t *sc, /**< Slave configuration. */
|
||||
uint16_t index, /**< SDO index. */
|
||||
uint8_t subindex, /**< SDO subindex. */
|
||||
size_t size /**< Data size to reserve. */
|
||||
)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
int ecrt_slave_config_dc(
|
||||
ec_slave_config_t *sc, /**< Slave configuration. */
|
||||
uint16_t assign_activate, /**< AssignActivate word. */
|
||||
uint32_t sync0_cycle, /**< SYNC0 cycle time [ns]. */
|
||||
int32_t sync0_shift, /**< SYNC0 shift time [ns]. */
|
||||
uint32_t sync1_cycle, /**< SYNC1 cycle time [ns]. */
|
||||
int32_t sync1_shift /**< SYNC1 shift time [ns]. */
|
||||
)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
int ecrt_slave_config_idn(
|
||||
ec_slave_config_t *sc, /**< Slave configuration. */
|
||||
uint8_t drive_no, /**< Drive number. */
|
||||
uint16_t idn, /**< SoE IDN. */
|
||||
ec_al_state_t state, /**< AL state in which to write the IDN (PREOP or
|
||||
SAFEOP). */
|
||||
const uint8_t *data, /**< Pointer to the data. */
|
||||
size_t size /**< Size of the \a data. */
|
||||
)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
int ecrt_slave_config_pdos(
|
||||
ec_slave_config_t *sc, /**< Slave configuration. */
|
||||
unsigned int n_syncs, /**< Number of sync manager configurations in
|
||||
\a syncs. */
|
||||
const ec_sync_info_t syncs[] /**< Array of sync manager
|
||||
configurations. */
|
||||
)
|
||||
{
|
||||
if (!syncs)
|
||||
return 0;
|
||||
for (unsigned int sync_idx = 0; sync_idx < n_syncs; ++sync_idx)
|
||||
{
|
||||
if (syncs[sync_idx].index == 0xff)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
auto &manager = sc->sync_managers[syncs[sync_idx].index];
|
||||
manager.dir = syncs[sync_idx].dir;
|
||||
for (unsigned int i = 0; i < syncs[sync_idx].n_pdos; ++i)
|
||||
{
|
||||
const auto &in_pdo = syncs[sync_idx].pdos[i];
|
||||
if (in_pdo.n_entries == 0 || !in_pdo.entries)
|
||||
{
|
||||
std::cerr << "Default mapping not supported.";
|
||||
return -1;
|
||||
}
|
||||
auto &out_pdo = manager.pdos[in_pdo.index];
|
||||
for (unsigned int pdo_entry_idx = 0; pdo_entry_idx < in_pdo.n_entries; ++pdo_entry_idx)
|
||||
{
|
||||
out_pdo.entries.push_back(in_pdo.entries[pdo_entry_idx]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
int ecrt_domain_reg_pdo_entry_list(
|
||||
ec_domain_t *domain, /**< Domain. */
|
||||
const ec_pdo_entry_reg_t *pdo_entry_regs /**< Array of PDO
|
||||
registrations. */
|
||||
)
|
||||
{
|
||||
const ec_pdo_entry_reg_t *reg;
|
||||
ec_slave_config_t *sc;
|
||||
int ret;
|
||||
|
||||
for (reg = pdo_entry_regs; reg->index; reg++)
|
||||
{
|
||||
if (!(sc = ecrt_master_slave_config(domain->getMaster(), reg->alias,
|
||||
reg->position, reg->vendor_id, reg->product_code)))
|
||||
return -ENOENT;
|
||||
|
||||
if ((ret = ecrt_slave_config_reg_pdo_entry(sc, reg->index,
|
||||
reg->subindex, domain, reg->bit_position)) < 0)
|
||||
return ret;
|
||||
|
||||
*reg->offset = ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ecrt_slave_config_reg_pdo_entry(
|
||||
ec_slave_config_t *sc, /**< Slave configuration. */
|
||||
uint16_t entry_index, /**< Index of the PDO entry to register. */
|
||||
uint8_t entry_subindex, /**< Subindex of the PDO entry to register. */
|
||||
ec_domain_t *domain, /**< Domain. */
|
||||
unsigned int *bit_position /**< Optional address if bit addressing
|
||||
is desired */
|
||||
)
|
||||
{
|
||||
for (auto sync_it : sc->sync_managers)
|
||||
{
|
||||
for (auto pdo_it : sync_it.second.pdos)
|
||||
{
|
||||
const auto offset = pdo_it.second.findEntry(entry_index, entry_subindex);
|
||||
if (offset != NotFound)
|
||||
{
|
||||
const auto domain_offset = domain->map(*sc, sync_it.first, pdo_it.first);
|
||||
if (domain_offset != -1)
|
||||
{
|
||||
if (bit_position)
|
||||
*bit_position = offset.bits;
|
||||
else if (!offset.bits)
|
||||
{
|
||||
std::cerr << "Pdo Entry is not byte aligned but bit offset is ignored!\n";
|
||||
return -1;
|
||||
}
|
||||
return domain_offset + offset.bytes;
|
||||
}
|
||||
else
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1; // offset
|
||||
}
|
||||
|
||||
int ecrt_slave_config_sdo8(
|
||||
ec_slave_config_t *sc, /**< Slave configuration */
|
||||
uint16_t sdo_index, /**< Index of the SDO to configure. */
|
||||
uint8_t sdo_subindex, /**< Subindex of the SDO to configure. */
|
||||
uint8_t value /**< Value to set. */
|
||||
)
|
||||
{
|
||||
return ecrt_slave_config_sdo(sc, sdo_index, sdo_subindex, &value, 1);
|
||||
}
|
||||
|
||||
int ecrt_slave_config_sdo16(
|
||||
ec_slave_config_t *sc, /**< Slave configuration */
|
||||
uint16_t sdo_index, /**< Index of the SDO to configure. */
|
||||
uint8_t sdo_subindex, /**< Subindex of the SDO to configure. */
|
||||
uint16_t const value /**< Value to set. */
|
||||
)
|
||||
{
|
||||
uint8_t buf[sizeof(value)];
|
||||
memcpy(&buf, &value, sizeof(value));
|
||||
return ecrt_slave_config_sdo(sc, sdo_index, sdo_subindex, buf, sizeof(buf));
|
||||
}
|
||||
|
||||
int ecrt_slave_config_sdo32(
|
||||
ec_slave_config_t *sc, /**< Slave configuration */
|
||||
uint16_t sdo_index, /**< Index of the SDO to configure. */
|
||||
uint8_t sdo_subindex, /**< Subindex of the SDO to configure. */
|
||||
uint32_t const value /**< Value to set. */
|
||||
)
|
||||
{
|
||||
uint8_t buf[sizeof(value)];
|
||||
memcpy(&buf, &value, sizeof(value));
|
||||
return ecrt_slave_config_sdo(sc, sdo_index, sdo_subindex, buf, sizeof(buf));
|
||||
}
|
||||
|
||||
int ecrt_slave_config_sdo(
|
||||
ec_slave_config_t *sc, /**< Slave configuration. */
|
||||
uint16_t index, /**< Index of the SDO to configure. */
|
||||
uint8_t subindex, /**< Subindex of the SDO to configure. */
|
||||
const uint8_t *data, /**< Pointer to the data. */
|
||||
size_t size /**< Size of the \a data. */
|
||||
)
|
||||
{
|
||||
sc->sdos[sdo_address{index, subindex}] = std::basic_string<uint8_t>(data, data + size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ecrt_write_lreal(void *data, double const value)
|
||||
{
|
||||
memcpy(data, &value, sizeof(value));
|
||||
}
|
||||
void ecrt_write_real(void *data, float const value)
|
||||
{
|
||||
memcpy(data, &value, sizeof(value));
|
||||
}
|
||||
float ecrt_read_real(const void *data)
|
||||
{
|
||||
float ans;
|
||||
memcpy(&ans, data, sizeof(ans));
|
||||
return ans;
|
||||
}
|
||||
|
||||
double ecrt_read_lreal(const void *data)
|
||||
{
|
||||
double ans;
|
||||
memcpy(&ans, data, sizeof(ans));
|
||||
return ans;
|
||||
}
|
||||
|
||||
void ec_slave_config::dumpJson(std::ostream &out, int indent) const
|
||||
{
|
||||
out << "{\n";
|
||||
indent += 4;
|
||||
add_spaces(out, indent);
|
||||
out << "\"vendor_id\": " << std::dec << vendor_id << ",\n";
|
||||
add_spaces(out, indent);
|
||||
out << "\"product_id\": " << product_code << ",\n";
|
||||
add_spaces(out, indent);
|
||||
out << "\"sdos\": ";
|
||||
map2Json(out, sdos, [](std::ostream &out, const std::basic_string<uint8_t> &value)
|
||||
{
|
||||
out << "\"0x";
|
||||
for (const auto s : value) {
|
||||
out << std::hex << std::setfill('0') << std::setw(2) << (unsigned)s;
|
||||
}
|
||||
out << '"'; }, indent);
|
||||
out << '\n';
|
||||
add_spaces(out, indent - 4);
|
||||
out << "}";
|
||||
}
|
||||
|
|
@ -0,0 +1,228 @@
|
|||
/*****************************************************************************
|
||||
*
|
||||
* Copyright (C) 2024 Bjarne von Horn, Ingenieurgemeinschaft IgH
|
||||
*
|
||||
* This file is part of the IgH EtherCAT master userspace library.
|
||||
*
|
||||
* The IgH EtherCAT master userspace library is free software; you can
|
||||
* redistribute it and/or modify it under the terms of the GNU Lesser General
|
||||
* Public License as published by the Free Software Foundation; version 2.1
|
||||
* of the License.
|
||||
*
|
||||
* The IgH EtherCAT master userspace library 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 Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with the IgH EtherCAT master userspace library. If not, see
|
||||
* <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ecrt.h>
|
||||
#include <rtipc.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct Offset
|
||||
{
|
||||
int bytes;
|
||||
int bits;
|
||||
|
||||
constexpr Offset(int bytes,
|
||||
int bits) : bytes(bytes), bits(bits) {}
|
||||
|
||||
constexpr bool operator!=(const Offset &other) const noexcept
|
||||
{
|
||||
return bytes != other.bytes || bits != other.bits;
|
||||
}
|
||||
};
|
||||
|
||||
constexpr Offset NotFound(-1, -1);
|
||||
|
||||
struct pdo
|
||||
{
|
||||
std::vector<ec_pdo_entry_info_t> entries;
|
||||
|
||||
size_t sizeInBytes() const;
|
||||
|
||||
Offset findEntry(uint16_t idx, uint8_t subindex) const;
|
||||
};
|
||||
|
||||
struct syncManager
|
||||
{
|
||||
ec_direction_t dir;
|
||||
std::map<uint16_t /* address */, pdo> pdos;
|
||||
};
|
||||
|
||||
class ec_address
|
||||
{
|
||||
uint32_t value;
|
||||
|
||||
public:
|
||||
ec_address(uint16_t alias, /**< Slave alias. */
|
||||
uint16_t position /**< Slave position. */)
|
||||
: value(static_cast<uint32_t>(alias) << 16 | position)
|
||||
{
|
||||
}
|
||||
|
||||
uint16_t getAlias() const { return value >> 16; }
|
||||
uint16_t getPosition() const { return value & 0xFFFF; }
|
||||
uint32_t getCombined() const { return value; }
|
||||
|
||||
bool operator<(const ec_address &other) const noexcept
|
||||
{
|
||||
return value < other.value;
|
||||
}
|
||||
|
||||
bool operator==(const ec_address &other) const noexcept
|
||||
{
|
||||
return value == other.value;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class sdo_address
|
||||
{
|
||||
uint32_t value;
|
||||
|
||||
public:
|
||||
sdo_address(uint16_t index, /**< Slave alias. */
|
||||
uint8_t subindex /**< Slave position. */)
|
||||
: value(static_cast<uint32_t>(index) << 8 | subindex)
|
||||
{
|
||||
}
|
||||
|
||||
uint16_t getIndex() const { return value >> 8; }
|
||||
uint8_t getSubIndex() const { return value & 0xFF; }
|
||||
uint32_t getCombined() const { return value; }
|
||||
|
||||
bool operator<(const sdo_address &other) const noexcept
|
||||
{
|
||||
return value < other.value;
|
||||
}
|
||||
|
||||
bool operator==(const sdo_address &other) const noexcept
|
||||
{
|
||||
return value == other.value;
|
||||
}
|
||||
};
|
||||
|
||||
struct ec_slave_config
|
||||
{
|
||||
ec_address address;
|
||||
uint32_t vendor_id; /**< Expected vendor ID. */
|
||||
uint32_t product_code; /**< Expected product code. */
|
||||
std::map<unsigned int, syncManager> sync_managers;
|
||||
std::map<sdo_address, std::basic_string<uint8_t>> sdos;
|
||||
|
||||
ec_slave_config(
|
||||
ec_address address,
|
||||
uint32_t vendor_id, /**< Expected vendor ID. */
|
||||
uint32_t product_code /**< Expected product code. */)
|
||||
: address(address), vendor_id(vendor_id), product_code(product_code)
|
||||
{
|
||||
}
|
||||
|
||||
void dumpJson(std::ostream &out, int indent) const;
|
||||
};
|
||||
|
||||
struct ec_domain
|
||||
{
|
||||
|
||||
private:
|
||||
struct PdoMap
|
||||
{
|
||||
size_t offset;
|
||||
size_t size_bytes;
|
||||
ec_address slave_address;
|
||||
unsigned int syncManager;
|
||||
uint16_t pdo_index;
|
||||
ec_direction_t dir;
|
||||
|
||||
PdoMap(
|
||||
size_t offset,
|
||||
size_t size_bytes,
|
||||
ec_address slave_address,
|
||||
unsigned int syncManager,
|
||||
uint16_t pdo_index,
|
||||
ec_direction_t dir)
|
||||
: offset(offset), size_bytes(size_bytes), slave_address(slave_address), syncManager(syncManager), pdo_index(pdo_index), dir(dir)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<uint8_t> data;
|
||||
std::vector<unsigned char> connected;
|
||||
std::vector<PdoMap> mapped_pdos;
|
||||
rtipc_group *rt_group;
|
||||
const char *prefix;
|
||||
ec_master_t *master;
|
||||
bool activated_ = false;
|
||||
size_t numSlaves = 0;
|
||||
|
||||
public:
|
||||
explicit ec_domain(struct rtipc *rtipc, const char *prefix, ec_master_t *master);
|
||||
|
||||
uint8_t *getData() const
|
||||
{
|
||||
if (!activated_)
|
||||
return nullptr;
|
||||
return const_cast<uint8_t *>(data.data());
|
||||
}
|
||||
|
||||
int activate(int domain_id);
|
||||
int process();
|
||||
int queue();
|
||||
|
||||
size_t getNumSlaves() const { return numSlaves; }
|
||||
|
||||
ssize_t map(ec_slave_config const &config, unsigned int syncManager,
|
||||
uint16_t pdo_index);
|
||||
|
||||
ec_master_t *getMaster() const { return master; }
|
||||
};
|
||||
|
||||
struct ec_master
|
||||
{
|
||||
private:
|
||||
struct RtIpcDeleter
|
||||
{
|
||||
void operator()(struct rtipc *r) const
|
||||
{
|
||||
rtipc_exit(r);
|
||||
}
|
||||
};
|
||||
|
||||
std::string rt_ipc_dir;
|
||||
std::string rt_ipc_name;
|
||||
std::list<ec_domain> domains;
|
||||
std::map<ec_address, ec_slave_config> slaves;
|
||||
std::unique_ptr<struct rtipc, RtIpcDeleter> rt_ipc;
|
||||
int id_;
|
||||
|
||||
public:
|
||||
explicit ec_master(int id);
|
||||
|
||||
int activate();
|
||||
ec_domain *createDomain();
|
||||
|
||||
int getNoSlaves() const { return slaves.size(); }
|
||||
int getId() const { return id_; }
|
||||
|
||||
ec_slave_config_t *slave_config(
|
||||
uint16_t alias, /**< Slave alias. */
|
||||
uint16_t position, /**< Slave position. */
|
||||
uint32_t vendor_id, /**< Expected vendor ID. */
|
||||
uint32_t product_code /**< Expected product code. */
|
||||
);
|
||||
};
|
||||
|
|
@ -82,6 +82,9 @@ LIBETHERCAT_1.5.2 {
|
|||
ecrt_voe_handler_received_header;
|
||||
ecrt_voe_handler_send_header;
|
||||
ecrt_voe_handler_write;
|
||||
local:
|
||||
# Hide all C++ symbols in libfakeethercat
|
||||
_Z*;
|
||||
};
|
||||
|
||||
LIBETHERCAT_1.5.3 {
|
||||
|
|
|
|||
|
|
@ -40,6 +40,9 @@
|
|||
A list of all native network card drivers can be found
|
||||
<a href="devicedrivers.html">here</a>.
|
||||
|
||||
A <a href="libfakeethercat.html">second userspace library</a> can be used for a dry-run mode
|
||||
or simulating Process Data.
|
||||
|
||||
For information how to build and install, see the INSTALL file in the source
|
||||
root.
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue