From 5f8c3ba5fccc5fc9a5ab71a9047c5d8b080c05b5 Mon Sep 17 00:00:00 2001 From: Hakan Bastedt Date: Sat, 14 Dec 2024 19:32:27 +0100 Subject: [PATCH] Firmware for EaserCAT-6000 brought in --- .../Firmware/.gitignore | 6 + .../Firmware/.vscode/extensions.json | 10 + .../Firmware/include/README | 39 + .../Firmware/include/extend32to64.h | 14 + .../Firmware/lib/README | 46 + .../soes-esi/MetalMusings_EaserCAT_6000.xml | 1137 +++++++++++ .../Firmware/lib/soes-esi/ecat_options.h | 42 + .../Firmware/lib/soes-esi/eeprom.bin | Bin 0 -> 2048 bytes .../Firmware/lib/soes-esi/eeprom.hex | 65 + .../Firmware/lib/soes-esi/esi.json | 225 +++ .../Firmware/lib/soes-esi/objectlist.c | 198 ++ .../Firmware/lib/soes-esi/utypes.h | 30 + .../Firmware/lib/soes/CMakeLists.txt | 29 + .../Firmware/lib/soes/Doxyfile | 1742 ++++++++++++++++ .../Firmware/lib/soes/cc.h | 88 + .../Firmware/lib/soes/doc/images/esi_pdo.png | Bin 0 -> 48677 bytes .../Firmware/lib/soes/doc/images/sii_pdo.png | Bin 0 -> 59537 bytes .../Firmware/lib/soes/doc/soes.dox | 50 + .../Firmware/lib/soes/doc/tutorial.txt | 476 +++++ .../Firmware/lib/soes/ecat_slv.c | 386 ++++ .../Firmware/lib/soes/ecat_slv.h | 69 + .../Firmware/lib/soes/esc.c | 1179 +++++++++++ .../Firmware/lib/soes/esc.h | 769 +++++++ .../Firmware/lib/soes/esc_coe.c | 1783 +++++++++++++++++ .../Firmware/lib/soes/esc_coe.h | 135 ++ .../Firmware/lib/soes/esc_eep.c | 80 + .../Firmware/lib/soes/esc_eep.h | 77 + .../Firmware/lib/soes/esc_eoe.c | 1066 ++++++++++ .../Firmware/lib/soes/esc_eoe.h | 68 + .../Firmware/lib/soes/esc_foe.c | 627 ++++++ .../Firmware/lib/soes/esc_foe.h | 77 + .../lib/soes/hal/arduino-lan9252/esc_hw.c | 458 +++++ .../lib/soes/hal/arduino-lan9252/spi.cpp | 66 + .../lib/soes/hal/arduino-lan9252/spi.h | 15 + .../lib/soes/hal/arduino-lan9252/spi.hpp | 25 + .../Firmware/platformio.ini | 23 + .../Firmware/src/extend32to64.cpp | 18 + .../Firmware/src/main.cpp | 237 +++ 38 files changed, 11355 insertions(+) create mode 100755 Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/.gitignore create mode 100755 Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/.vscode/extensions.json create mode 100755 Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/include/README create mode 100755 Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/include/extend32to64.h create mode 100755 Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/README create mode 100755 Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes-esi/MetalMusings_EaserCAT_6000.xml create mode 100755 Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes-esi/ecat_options.h create mode 100755 Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes-esi/eeprom.bin create mode 100755 Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes-esi/eeprom.hex create mode 100755 Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes-esi/esi.json create mode 100755 Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes-esi/objectlist.c create mode 100755 Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes-esi/utypes.h create mode 100755 Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/CMakeLists.txt create mode 100755 Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/Doxyfile create mode 100755 Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/cc.h create mode 100755 Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/doc/images/esi_pdo.png create mode 100755 Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/doc/images/sii_pdo.png create mode 100755 Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/doc/soes.dox create mode 100755 Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/doc/tutorial.txt create mode 100755 Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/ecat_slv.c create mode 100755 Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/ecat_slv.h create mode 100755 Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/esc.c create mode 100755 Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/esc.h create mode 100755 Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/esc_coe.c create mode 100755 Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/esc_coe.h create mode 100755 Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/esc_eep.c create mode 100755 Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/esc_eep.h create mode 100755 Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/esc_eoe.c create mode 100755 Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/esc_eoe.h create mode 100755 Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/esc_foe.c create mode 100755 Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/esc_foe.h create mode 100755 Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/hal/arduino-lan9252/esc_hw.c create mode 100755 Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/hal/arduino-lan9252/spi.cpp create mode 100755 Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/hal/arduino-lan9252/spi.h create mode 100755 Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/hal/arduino-lan9252/spi.hpp create mode 100755 Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/platformio.ini create mode 100755 Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/src/extend32to64.cpp create mode 100755 Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/src/main.cpp diff --git a/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/.gitignore b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/.gitignore new file mode 100755 index 0000000..fb3ca12 --- /dev/null +++ b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/.gitignore @@ -0,0 +1,6 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch +.vscode/settings.json diff --git a/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/.vscode/extensions.json b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/.vscode/extensions.json new file mode 100755 index 0000000..080e70d --- /dev/null +++ b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ], + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] +} diff --git a/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/include/README b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/include/README new file mode 100755 index 0000000..45496b1 --- /dev/null +++ b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/include/extend32to64.h b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/include/extend32to64.h new file mode 100755 index 0000000..b6c3b0d --- /dev/null +++ b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/include/extend32to64.h @@ -0,0 +1,14 @@ +#ifndef EXTEND32TO64 +#define EXTEND32TO54 + +#include + +class extend32to64 +{ +public: + int64_t previousTimeValue = 0; + const uint64_t ONE_PERIOD = 4294967296; // almost UINT32_MAX; + const uint64_t HALF_PERIOD = 2147483648; // Half of that + int64_t extendTime(uint32_t in); +}; +#endif diff --git a/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/README b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/README new file mode 100755 index 0000000..8c9c29c --- /dev/null +++ b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes-esi/MetalMusings_EaserCAT_6000.xml b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes-esi/MetalMusings_EaserCAT_6000.xml new file mode 100755 index 0000000..d83d547 --- /dev/null +++ b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes-esi/MetalMusings_EaserCAT_6000.xml @@ -0,0 +1,1137 @@ + + + + 2730 + MetalMusings + + + + + MachineControl + Incremental encoder + + + + + EaserCAT 6000 + MetalMusings EaserCAT 6000 + MachineControl + + 5001 + 0 + + + + DT1018 + 144 + + 0 + Max SubIndex + USINT + 8 + 0 + + ro + + + + 1 + Vendor ID + UDINT + 32 + 16 + + ro + + + + 2 + Product Code + UDINT + 32 + 48 + + ro + + + + 3 + Revision Number + UDINT + 32 + 80 + + ro + + + + 4 + Serial Number + UDINT + 32 + 112 + + ro + + + + + DT1600 + 144 + + 0 + Max SubIndex + USINT + 8 + 0 + + ro + + + + 1 + New array subitem + UDINT + 32 + 16 + + ro + + + + 2 + New array subitem + UDINT + 32 + 48 + + ro + + + + 3 + New array subitem + UDINT + 32 + 80 + + ro + + + + 4 + New array subitem + UDINT + 32 + 112 + + ro + + + + + DT1A00 + 400 + + 0 + Max SubIndex + USINT + 8 + 0 + + ro + + + + 1 + New array subitem + UDINT + 32 + 16 + + ro + + + + 2 + New array subitem + UDINT + 32 + 48 + + ro + + + + 3 + New array subitem + UDINT + 32 + 80 + + ro + + + + 4 + New array subitem + UDINT + 32 + 112 + + ro + + + + 5 + New array subitem + UDINT + 32 + 144 + + ro + + + + 6 + New array subitem + UDINT + 32 + 176 + + ro + + + + 7 + New array subitem + UDINT + 32 + 208 + + ro + + + + 8 + New array subitem + UDINT + 32 + 240 + + ro + + + + 9 + New array subitem + UDINT + 32 + 272 + + ro + + + + 10 + New array subitem + UDINT + 32 + 304 + + ro + + + + 11 + New array subitem + UDINT + 32 + 336 + + ro + + + + 12 + New array subitem + UDINT + 32 + 368 + + ro + + + + + DT1A01 + 48 + + 0 + Max SubIndex + USINT + 8 + 0 + + ro + + + + 1 + Velocity + UDINT + 32 + 16 + + ro + + + + + DT1C00ARR + USINT + 32 + + 1 + 4 + + + + DT1C00 + 48 + + 0 + Max SubIndex + USINT + 8 + 0 + + ro + + + + Elements + DT1C00ARR + 32 + 16 + + ro + + + + + DT1C12ARR + UINT + 16 + + 1 + 1 + + + + DT1C12 + 32 + + 0 + Max SubIndex + USINT + 8 + 0 + + ro + + + + Elements + DT1C12ARR + 16 + 16 + + ro + + + + + DT1C13ARR + UINT + 32 + + 1 + 2 + + + + DT1C13 + 48 + + 0 + Max SubIndex + USINT + 8 + 0 + + ro + + + + Elements + DT1C13ARR + 32 + 16 + + ro + + + + + DT6000ARR + BOOL + 12 + + 1 + 12 + + + + DT6000 + 28 + + 0 + Max SubIndex + USINT + 8 + 0 + + ro + + + + Elements + DT6000ARR + 12 + 16 + + ro + T + + + + + DT7000ARR + BOOL + 4 + + 1 + 4 + + + + DT7000 + 20 + + 0 + Max SubIndex + USINT + 8 + 0 + + ro + + + + Elements + DT7000ARR + 4 + 16 + + ro + R + + + + + UDINT + 32 + + + STRING(26) + 208 + + + STRING(5) + 40 + + + USINT + 8 + + + UINT + 16 + + + REAL + 32 + + + BOOL + 1 + + + + + #x1000 + Device Type + UDINT + 32 + + 5001 + + + ro + m + + + + #x1008 + Device Name + STRING(26) + 208 + + MetalMusings EaserCAT 6000 + + + ro + + + + #x1009 + Hardware Version + STRING(5) + 40 + + 0.0.1 + + + ro + o + + + + #x100A + Software Version + STRING(5) + 40 + + 0.0.1 + + + ro + + + + #x1018 + Identity Object + DT1018 + 144 + + + Max SubIndex + + 4 + + + + Vendor ID + + 2730 + + + + Product Code + + 13294767 + + + + Revision Number + + 3 + + + + Serial Number + + 1 + + + + + ro + + + + #x1600 + Output + DT1600 + 144 + + + Max SubIndex + + 4 + + + + New array subitem + + #x70000101 + + + + New array subitem + + #x70000201 + + + + New array subitem + + #x70000301 + + + + New array subitem + + #x70000401 + + + + + ro + + + + #x1A00 + Input + DT1A00 + 400 + + + Max SubIndex + + 12 + + + + New array subitem + + #x60000101 + + + + New array subitem + + #x60000201 + + + + New array subitem + + #x60000301 + + + + New array subitem + + #x60000401 + + + + New array subitem + + #x60000501 + + + + New array subitem + + #x60000601 + + + + New array subitem + + #x60000701 + + + + New array subitem + + #x60000801 + + + + New array subitem + + #x60000901 + + + + New array subitem + + #x60000a01 + + + + New array subitem + + #x60000b01 + + + + New array subitem + + #x60000c01 + + + + + ro + + + + #x1A01 + Velocity + DT1A01 + 48 + + + Max SubIndex + + 1 + + + + Velocity + + #x60010020 + + + + + ro + + + + #x1C00 + Sync Manager Communication Type + DT1C00 + 48 + + + Max SubIndex + + 4 + + + + Communications Type SM0 + + 1 + + + + Communications Type SM1 + + 2 + + + + Communications Type SM2 + + 3 + + + + Communications Type SM3 + + 4 + + + + + ro + + + + #x1C12 + Sync Manager 2 PDO Assignment + DT1C12 + 32 + + + Max SubIndex + + 1 + + + + PDO Mapping + + #x1600 + + + + + ro + + + + #x1C13 + Sync Manager 3 PDO Assignment + DT1C13 + 48 + + + Max SubIndex + + 2 + + + + PDO Mapping + + #x1A00 + + + + PDO Mapping + + #x1A01 + + + + + ro + + + + #x2000 + VelocityScale + REAL + 32 + + 0 + + + ro + + + + #x6000 + Input + DT6000 + 28 + + + Max SubIndex + + 12 + + + + New array subitem + + 0 + + + + New array subitem + + 0 + + + + New array subitem + + 0 + + + + New array subitem + + 0 + + + + New array subitem + + 0 + + + + New array subitem + + 0 + + + + New array subitem + + 0 + + + + New array subitem + + 0 + + + + New array subitem + + 0 + + + + New array subitem + + 0 + + + + New array subitem + + 0 + + + + New array subitem + + 0 + + + + + ro + + + + #x6001 + Velocity + REAL + 32 + + 0 + + + ro + T + + + + #x7000 + Output + DT7000 + 20 + + + Max SubIndex + + 4 + + + + New array subitem + + 0 + + + + New array subitem + + 0 + + + + New array subitem + + 0 + + + + New array subitem + + 0 + + + + + ro + + + + + + Outputs + Inputs + MBoxState + MBoxOut + MBoxIn + Outputs + Inputs + + #x1600 + Output + + #x7000 + #x1 + 1 + New array subitem + BOOL + + + #x7000 + #x2 + 1 + New array subitem + BOOL + + + #x7000 + #x3 + 1 + New array subitem + BOOL + + + #x7000 + #x4 + 1 + New array subitem + BOOL + + + + #x1A00 + Input + + #x6000 + #x1 + 1 + New array subitem + BOOL + + + #x6000 + #x2 + 1 + New array subitem + BOOL + + + #x6000 + #x3 + 1 + New array subitem + BOOL + + + #x6000 + #x4 + 1 + New array subitem + BOOL + + + #x6000 + #x5 + 1 + New array subitem + BOOL + + + #x6000 + #x6 + 1 + New array subitem + BOOL + + + #x6000 + #x7 + 1 + New array subitem + BOOL + + + #x6000 + #x8 + 1 + New array subitem + BOOL + + + #x6000 + #x9 + 1 + New array subitem + BOOL + + + #x6000 + #xa + 1 + New array subitem + BOOL + + + #x6000 + #xb + 1 + New array subitem + BOOL + + + #x6000 + #xc + 1 + New array subitem + BOOL + + + + #x1A01 + Velocity + + #x6001 + #x0 + 32 + Velocity + REAL + + + + + + + + SM-Synchron + SM-Synchron + #x000 + + + DC + DC-Synchron + #x300 + + + + 2048 + 80060344640000 + + + + + \ No newline at end of file diff --git a/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes-esi/ecat_options.h b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes-esi/ecat_options.h new file mode 100755 index 0000000..6f10889 --- /dev/null +++ b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes-esi/ecat_options.h @@ -0,0 +1,42 @@ +#ifndef __ECAT_OPTIONS_H__ +#define __ECAT_OPTIONS_H__ + +#define USE_FOE 0 +#define USE_EOE 0 + +#define MBXSIZE 512 +#define MBXSIZEBOOT 512 +#define MBXBUFFERS 3 + +#define MBX0_sma 0x1000 +#define MBX0_sml MBXSIZE +#define MBX0_sme MBX0_sma+MBX0_sml-1 +#define MBX0_smc 0x26 +#define MBX1_sma MBX0_sma+MBX0_sml +#define MBX1_sml MBXSIZE +#define MBX1_sme MBX1_sma+MBX1_sml-1 +#define MBX1_smc 0x22 + +#define MBX0_sma_b 0x1000 +#define MBX0_sml_b MBXSIZEBOOT +#define MBX0_sme_b MBX0_sma_b+MBX0_sml_b-1 +#define MBX0_smc_b 0x26 +#define MBX1_sma_b MBX0_sma_b+MBX0_sml_b +#define MBX1_sml_b MBXSIZEBOOT +#define MBX1_sme_b MBX1_sma_b+MBX1_sml_b-1 +#define MBX1_smc_b 0x22 + +#define SM2_sma 0x1600 +#define SM2_smc 0x24 +#define SM2_act 1 +#define SM3_sma 0x1A00 +#define SM3_smc 0x20 +#define SM3_act 1 + +#define MAX_MAPPINGS_SM2 4 +#define MAX_MAPPINGS_SM3 13 + +#define MAX_RXPDO_SIZE 512 +#define MAX_TXPDO_SIZE 512 + +#endif /* __ECAT_OPTIONS_H__ */ diff --git a/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes-esi/eeprom.bin b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes-esi/eeprom.bin new file mode 100755 index 0000000000000000000000000000000000000000..60cff241f75f6ff6f2a64cb66eeee4e8a5785427 GIT binary patch literal 2048 zcmZo*V|GblfB_MPRa^`V>+hUmUIM5_5b@i!<}mixp5b$T0vN z#mvaUAdhf}Ac6^IX#kzg$i&Q`2~-PGuExN~2=a##P?SN8fkB0Vk(oh?0q7b=mj9z* UGz3ONU^E0qLtr!nhIt480MIG_%K!iX literal 0 HcmV?d00001 diff --git a/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes-esi/eeprom.hex b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes-esi/eeprom.hex new file mode 100755 index 0000000..d157a2e --- /dev/null +++ b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes-esi/eeprom.hex @@ -0,0 +1,65 @@ +:2000000080060344640000000000000000001400AA0A0000AFDCCA0003000000010000008E +:20002000000000000000000000000000000000000010000200120002040000000000000096 +:200040000000000000000000000000000000000000000000000000000000000000000000A0 +:20006000000000000000000000000000000000000000000000000000000000000F00010070 +:200080000A002000040D456173657243415420363030300E4D616368696E65436F6E7472AE +:2000A0006F6C06494D474342591A4D6574616C4D7573696E677320456173657243415420A9 +:2000C000363030301E00100002030104001F000000000000000000001100000000000000F2 +:2000E000000000000000000028000200010203002900100000100002260001010012000249 +:20010000220001020016000024000103001A000020000104FFFFFFFFFFFFFFFFFFFFFFFF49 +:20012000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDF +:20014000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBF +:20016000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9F +:20018000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7F +:2001A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5F +:2001C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3F +:2001E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1F +:20020000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE +:20022000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDE +:20024000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBE +:20026000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9E +:20028000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7E +:2002A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5E +:2002C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3E +:2002E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1E +:20030000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD +:20032000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDD +:20034000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBD +:20036000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9D +:20038000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7D +:2003A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D +:2003C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3D +:2003E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1D +:20040000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC +:20042000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDC +:20044000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBC +:20046000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9C +:20048000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7C +:2004A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5C +:2004C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3C +:2004E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1C +:20050000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB +:20052000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDB +:20054000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBB +:20056000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9B +:20058000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7B +:2005A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5B +:2005C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3B +:2005E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1B +:20060000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA +:20062000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDA +:20064000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBA +:20066000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9A +:20068000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7A +:2006A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5A +:2006C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3A +:2006E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1A +:20070000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9 +:20072000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD9 +:20074000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB9 +:20076000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF99 +:20078000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF79 +:2007A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF59 +:2007C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF39 +:2007E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF19 +:00000001FF \ No newline at end of file diff --git a/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes-esi/esi.json b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes-esi/esi.json new file mode 100755 index 0000000..8ab241d --- /dev/null +++ b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes-esi/esi.json @@ -0,0 +1,225 @@ +{ + "form": { + "VendorName": "MetalMusings", + "VendorID": "0xaaa", + "ProductCode": "0xcadcaf", + "ProfileNo": "5001", + "RevisionNumber": "0x003", + "SerialNumber": "0x001", + "HWversion": "0.0.1", + "SWversion": "0.0.1", + "EEPROMsize": "2048", + "RxMailboxOffset": "0x1000", + "TxMailboxOffset": "0x1200", + "MailboxSize": "512", + "SM2Offset": "0x1600", + "SM3Offset": "0x1A00", + "TextGroupType": "MachineControl", + "TextGroupName5": "Incremental encoder", + "ImageName": "IMGCBY", + "TextDeviceType": "EaserCAT 6000", + "TextDeviceName": "MetalMusings EaserCAT 6000", + "Port0Physical": "Y", + "Port1Physical": "Y", + "Port2Physical": " ", + "Port3Physical": " ", + "ESC": "LAN9252", + "SPImode": "3", + "CoeDetailsEnableSDO": "EnableSDO", + "CoeDetailsEnableSDOInfo": "EnableSDOInfo", + "CoeDetailsEnablePDOAssign": "EnablePDOAssign", + "CoeDetailsEnablePDOConfiguration": "EnablePDOConfiguration", + "CoeDetailsEnableUploadAtStartup": "EnableUploadAtStartup", + "CoeDetailsEnableSDOCompleteAccess": "EnableSDOCompleteAccess" + }, + "od": { + "sdo": { + "2000": { + "otype": "VAR", + "name": "VelocityScale", + "access": "RO", + "dtype": "REAL32", + "value": "0", + "isSDOitem": true, + "data": "&Obj.VelocityScale" + }, + "A": { + "otype": "RECORD", + "name": "Error Settings", + "access": "RO", + "items": [ + { + "name": "Max SubIndex" + }, + { + "name": "New record subitem", + "dtype": "UNSIGNED8" + } + ] + } + }, + "txpdo": { + "600": { + "otype": "VAR", + "name": "EncFrequency", + "access": "RO", + "pdo_mappings": [ + "txpdo" + ], + "dtype": "INTEGER32", + "value": "0", + "data": "&Obj.EncFrequency" + }, + "6000": { + "otype": "ARRAY", + "name": "Input", + "access": "RO", + "items": [ + { + "name": "Max SubIndex" + }, + { + "name": "New array subitem", + "data": "&Obj.Input[0]", + "value": "0" + }, + { + "name": "New array subitem", + "value": "0", + "data": "&Obj.Input[1]" + }, + { + "name": "New array subitem", + "value": "0", + "data": "&Obj.Input[2]" + }, + { + "name": "New array subitem", + "value": "0", + "data": "&Obj.Input[3]" + }, + { + "name": "New array subitem", + "value": "0", + "data": "&Obj.Input[4]" + }, + { + "name": "New array subitem", + "value": "0", + "data": "&Obj.Input[5]" + }, + { + "name": "New array subitem", + "value": "0", + "data": "&Obj.Input[6]" + }, + { + "name": "New array subitem", + "value": "0", + "data": "&Obj.Input[7]" + }, + { + "name": "New array subitem", + "value": "0", + "data": "&Obj.Input[8]" + }, + { + "name": "New array subitem", + "value": "0", + "data": "&Obj.Input[9]" + }, + { + "name": "New array subitem", + "value": "0", + "data": "&Obj.Input[10]" + }, + { + "name": "New array subitem", + "value": "0", + "data": "&Obj.Input[11]" + } + ], + "pdo_mappings": [ + "txpdo" + ], + "dtype": "BOOLEAN" + }, + "6001": { + "otype": "VAR", + "name": "Velocity", + "access": "RO", + "pdo_mappings": [ + "txpdo" + ], + "dtype": "REAL32", + "value": "0", + "data": "&Obj.Velocity" + } + }, + "rxpdo": { + "7000": { + "otype": "ARRAY", + "name": "Output", + "access": "RO", + "items": [ + { + "name": "Max SubIndex" + }, + { + "name": "New array subitem", + "data": "&Obj.Output[0]" + }, + { + "name": "New array subitem", + "value": "0", + "data": "&Obj.Output[1]" + }, + { + "name": "New array subitem", + "value": "0", + "data": "&Obj.Output[2]" + }, + { + "name": "New array subitem", + "value": "0", + "data": "&Obj.Output[3]" + } + ], + "pdo_mappings": [ + "rxpdo" + ], + "dtype": "BOOLEAN" + }, + "60664": { + "otype": "VAR", + "name": "ActualPosition", + "access": "RO", + "pdo_mappings": [ + "rxpdo" + ], + "dtype": "INTEGER32", + "value": "0" + } + } + }, + "dc": [ + { + "Name": "SM-Synchron", + "Description": "SM-Synchron", + "AssignActivate": "#x000", + "Sync0cycleTime": "0", + "Sync0shiftTime": "0", + "Sync1cycleTime": "0", + "Sync1shiftTime": "0" + }, + { + "Name": "DC", + "Description": "DC-Synchron", + "AssignActivate": "#x300", + "Sync0cycleTime": "0", + "Sync0shiftTime": "0", + "Sync1cycleTime": "0", + "Sync1shiftTime": "0" + } + ] +} \ No newline at end of file diff --git a/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes-esi/objectlist.c b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes-esi/objectlist.c new file mode 100755 index 0000000..6a74bed --- /dev/null +++ b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes-esi/objectlist.c @@ -0,0 +1,198 @@ +#include "esc_coe.h" +#include "utypes.h" +#include + + +static const char acName1000[] = "Device Type"; +static const char acName1008[] = "Device Name"; +static const char acName1009[] = "Hardware Version"; +static const char acName100A[] = "Software Version"; +static const char acName1018[] = "Identity Object"; +static const char acName1018_00[] = "Max SubIndex"; +static const char acName1018_01[] = "Vendor ID"; +static const char acName1018_02[] = "Product Code"; +static const char acName1018_03[] = "Revision Number"; +static const char acName1018_04[] = "Serial Number"; +static const char acName1600[] = "Output"; +static const char acName1600_00[] = "Max SubIndex"; +static const char acName1600_01[] = "New array subitem"; +static const char acName1600_02[] = "New array subitem"; +static const char acName1600_03[] = "New array subitem"; +static const char acName1600_04[] = "New array subitem"; +static const char acName1A00[] = "Input"; +static const char acName1A00_00[] = "Max SubIndex"; +static const char acName1A00_01[] = "New array subitem"; +static const char acName1A00_02[] = "New array subitem"; +static const char acName1A00_03[] = "New array subitem"; +static const char acName1A00_04[] = "New array subitem"; +static const char acName1A00_05[] = "New array subitem"; +static const char acName1A00_06[] = "New array subitem"; +static const char acName1A00_07[] = "New array subitem"; +static const char acName1A00_08[] = "New array subitem"; +static const char acName1A00_09[] = "New array subitem"; +static const char acName1A00_0a[] = "New array subitem"; +static const char acName1A00_0b[] = "New array subitem"; +static const char acName1A00_0c[] = "New array subitem"; +static const char acName1A01[] = "Velocity"; +static const char acName1A01_00[] = "Max SubIndex"; +static const char acName1A01_01[] = "Velocity"; +static const char acName1C00[] = "Sync Manager Communication Type"; +static const char acName1C00_00[] = "Max SubIndex"; +static const char acName1C00_01[] = "Communications Type SM0"; +static const char acName1C00_02[] = "Communications Type SM1"; +static const char acName1C00_03[] = "Communications Type SM2"; +static const char acName1C00_04[] = "Communications Type SM3"; +static const char acName1C12[] = "Sync Manager 2 PDO Assignment"; +static const char acName1C12_00[] = "Max SubIndex"; +static const char acName1C12_01[] = "PDO Mapping"; +static const char acName1C13[] = "Sync Manager 3 PDO Assignment"; +static const char acName1C13_00[] = "Max SubIndex"; +static const char acName1C13_01[] = "PDO Mapping"; +static const char acName1C13_02[] = "PDO Mapping"; +static const char acName2000[] = "VelocityScale"; +static const char acName6000[] = "Input"; +static const char acName6000_00[] = "Max SubIndex"; +static const char acName6000_01[] = "New array subitem"; +static const char acName6000_02[] = "New array subitem"; +static const char acName6000_03[] = "New array subitem"; +static const char acName6000_04[] = "New array subitem"; +static const char acName6000_05[] = "New array subitem"; +static const char acName6000_06[] = "New array subitem"; +static const char acName6000_07[] = "New array subitem"; +static const char acName6000_08[] = "New array subitem"; +static const char acName6000_09[] = "New array subitem"; +static const char acName6000_0a[] = "New array subitem"; +static const char acName6000_0b[] = "New array subitem"; +static const char acName6000_0c[] = "New array subitem"; +static const char acName6001[] = "Velocity"; +static const char acName7000[] = "Output"; +static const char acName7000_00[] = "Max SubIndex"; +static const char acName7000_01[] = "New array subitem"; +static const char acName7000_02[] = "New array subitem"; +static const char acName7000_03[] = "New array subitem"; +static const char acName7000_04[] = "New array subitem"; + +const _objd SDO1000[] = +{ + {0x0, DTYPE_UNSIGNED32, 32, ATYPE_RO, acName1000, 5001, NULL}, +}; +const _objd SDO1008[] = +{ + {0x0, DTYPE_VISIBLE_STRING, 208, ATYPE_RO, acName1008, 0, "MetalMusings EaserCAT 6000"}, +}; +const _objd SDO1009[] = +{ + {0x0, DTYPE_VISIBLE_STRING, 40, ATYPE_RO, acName1009, 0, "0.0.1"}, +}; +const _objd SDO100A[] = +{ + {0x0, DTYPE_VISIBLE_STRING, 40, ATYPE_RO, acName100A, 0, "0.0.1"}, +}; +const _objd SDO1018[] = +{ + {0x00, DTYPE_UNSIGNED8, 8, ATYPE_RO, acName1018_00, 4, NULL}, + {0x01, DTYPE_UNSIGNED32, 32, ATYPE_RO, acName1018_01, 2730, NULL}, + {0x02, DTYPE_UNSIGNED32, 32, ATYPE_RO, acName1018_02, 13294767, NULL}, + {0x03, DTYPE_UNSIGNED32, 32, ATYPE_RO, acName1018_03, 3, NULL}, + {0x04, DTYPE_UNSIGNED32, 32, ATYPE_RO, acName1018_04, 1, &Obj.serial}, +}; +const _objd SDO1600[] = +{ + {0x00, DTYPE_UNSIGNED8, 8, ATYPE_RO, acName1600_00, 4, NULL}, + {0x01, DTYPE_UNSIGNED32, 32, ATYPE_RO, acName1600_01, 0x70000101, NULL}, + {0x02, DTYPE_UNSIGNED32, 32, ATYPE_RO, acName1600_02, 0x70000201, NULL}, + {0x03, DTYPE_UNSIGNED32, 32, ATYPE_RO, acName1600_03, 0x70000301, NULL}, + {0x04, DTYPE_UNSIGNED32, 32, ATYPE_RO, acName1600_04, 0x70000401, NULL}, +}; +const _objd SDO1A00[] = +{ + {0x00, DTYPE_UNSIGNED8, 8, ATYPE_RO, acName1A00_00, 12, NULL}, + {0x01, DTYPE_UNSIGNED32, 32, ATYPE_RO, acName1A00_01, 0x60000101, NULL}, + {0x02, DTYPE_UNSIGNED32, 32, ATYPE_RO, acName1A00_02, 0x60000201, NULL}, + {0x03, DTYPE_UNSIGNED32, 32, ATYPE_RO, acName1A00_03, 0x60000301, NULL}, + {0x04, DTYPE_UNSIGNED32, 32, ATYPE_RO, acName1A00_04, 0x60000401, NULL}, + {0x05, DTYPE_UNSIGNED32, 32, ATYPE_RO, acName1A00_05, 0x60000501, NULL}, + {0x06, DTYPE_UNSIGNED32, 32, ATYPE_RO, acName1A00_06, 0x60000601, NULL}, + {0x07, DTYPE_UNSIGNED32, 32, ATYPE_RO, acName1A00_07, 0x60000701, NULL}, + {0x08, DTYPE_UNSIGNED32, 32, ATYPE_RO, acName1A00_08, 0x60000801, NULL}, + {0x09, DTYPE_UNSIGNED32, 32, ATYPE_RO, acName1A00_09, 0x60000901, NULL}, + {0x0a, DTYPE_UNSIGNED32, 32, ATYPE_RO, acName1A00_0a, 0x60000a01, NULL}, + {0x0b, DTYPE_UNSIGNED32, 32, ATYPE_RO, acName1A00_0b, 0x60000b01, NULL}, + {0x0c, DTYPE_UNSIGNED32, 32, ATYPE_RO, acName1A00_0c, 0x60000c01, NULL}, +}; +const _objd SDO1A01[] = +{ + {0x00, DTYPE_UNSIGNED8, 8, ATYPE_RO, acName1A01_00, 1, NULL}, + {0x01, DTYPE_UNSIGNED32, 32, ATYPE_RO, acName1A01_01, 0x60010020, NULL}, +}; +const _objd SDO1C00[] = +{ + {0x00, DTYPE_UNSIGNED8, 8, ATYPE_RO, acName1C00_00, 4, NULL}, + {0x01, DTYPE_UNSIGNED8, 8, ATYPE_RO, acName1C00_01, 1, NULL}, + {0x02, DTYPE_UNSIGNED8, 8, ATYPE_RO, acName1C00_02, 2, NULL}, + {0x03, DTYPE_UNSIGNED8, 8, ATYPE_RO, acName1C00_03, 3, NULL}, + {0x04, DTYPE_UNSIGNED8, 8, ATYPE_RO, acName1C00_04, 4, NULL}, +}; +const _objd SDO1C12[] = +{ + {0x00, DTYPE_UNSIGNED8, 8, ATYPE_RO, acName1C12_00, 1, NULL}, + {0x01, DTYPE_UNSIGNED16, 16, ATYPE_RO, acName1C12_01, 0x1600, NULL}, +}; +const _objd SDO1C13[] = +{ + {0x00, DTYPE_UNSIGNED8, 8, ATYPE_RO, acName1C13_00, 2, NULL}, + {0x01, DTYPE_UNSIGNED16, 16, ATYPE_RO, acName1C13_01, 0x1A00, NULL}, + {0x02, DTYPE_UNSIGNED16, 16, ATYPE_RO, acName1C13_02, 0x1A01, NULL}, +}; +const _objd SDO2000[] = +{ + {0x0, DTYPE_REAL32, 32, ATYPE_RO, acName2000, 0x00000000, &Obj.VelocityScale}, +}; +const _objd SDO6000[] = +{ + {0x00, DTYPE_UNSIGNED8, 8, ATYPE_RO, acName6000_00, 12, NULL}, + {0x01, DTYPE_BOOLEAN, 1, ATYPE_RO | ATYPE_TXPDO, acName6000_01, 0, &Obj.Input[0]}, + {0x02, DTYPE_BOOLEAN, 1, ATYPE_RO | ATYPE_TXPDO, acName6000_02, 0, &Obj.Input[1]}, + {0x03, DTYPE_BOOLEAN, 1, ATYPE_RO | ATYPE_TXPDO, acName6000_03, 0, &Obj.Input[2]}, + {0x04, DTYPE_BOOLEAN, 1, ATYPE_RO | ATYPE_TXPDO, acName6000_04, 0, &Obj.Input[3]}, + {0x05, DTYPE_BOOLEAN, 1, ATYPE_RO | ATYPE_TXPDO, acName6000_05, 0, &Obj.Input[4]}, + {0x06, DTYPE_BOOLEAN, 1, ATYPE_RO | ATYPE_TXPDO, acName6000_06, 0, &Obj.Input[5]}, + {0x07, DTYPE_BOOLEAN, 1, ATYPE_RO | ATYPE_TXPDO, acName6000_07, 0, &Obj.Input[6]}, + {0x08, DTYPE_BOOLEAN, 1, ATYPE_RO | ATYPE_TXPDO, acName6000_08, 0, &Obj.Input[7]}, + {0x09, DTYPE_BOOLEAN, 1, ATYPE_RO | ATYPE_TXPDO, acName6000_09, 0, &Obj.Input[8]}, + {0x0a, DTYPE_BOOLEAN, 1, ATYPE_RO | ATYPE_TXPDO, acName6000_0a, 0, &Obj.Input[9]}, + {0x0b, DTYPE_BOOLEAN, 1, ATYPE_RO | ATYPE_TXPDO, acName6000_0b, 0, &Obj.Input[10]}, + {0x0c, DTYPE_BOOLEAN, 1, ATYPE_RO | ATYPE_TXPDO, acName6000_0c, 0, &Obj.Input[11]}, +}; +const _objd SDO6001[] = +{ + {0x0, DTYPE_REAL32, 32, ATYPE_RO | ATYPE_TXPDO, acName6001, 0x00000000, &Obj.Velocity}, +}; +const _objd SDO7000[] = +{ + {0x00, DTYPE_UNSIGNED8, 8, ATYPE_RO, acName7000_00, 4, NULL}, + {0x01, DTYPE_BOOLEAN, 1, ATYPE_RO | ATYPE_RXPDO, acName7000_01, 0, &Obj.Output[0]}, + {0x02, DTYPE_BOOLEAN, 1, ATYPE_RO | ATYPE_RXPDO, acName7000_02, 0, &Obj.Output[1]}, + {0x03, DTYPE_BOOLEAN, 1, ATYPE_RO | ATYPE_RXPDO, acName7000_03, 0, &Obj.Output[2]}, + {0x04, DTYPE_BOOLEAN, 1, ATYPE_RO | ATYPE_RXPDO, acName7000_04, 0, &Obj.Output[3]}, +}; + +const _objectlist SDOobjects[] = +{ + {0x1000, OTYPE_VAR, 0, 0, acName1000, SDO1000}, + {0x1008, OTYPE_VAR, 0, 0, acName1008, SDO1008}, + {0x1009, OTYPE_VAR, 0, 0, acName1009, SDO1009}, + {0x100A, OTYPE_VAR, 0, 0, acName100A, SDO100A}, + {0x1018, OTYPE_RECORD, 4, 0, acName1018, SDO1018}, + {0x1600, OTYPE_RECORD, 4, 0, acName1600, SDO1600}, + {0x1A00, OTYPE_RECORD, 12, 0, acName1A00, SDO1A00}, + {0x1A01, OTYPE_RECORD, 1, 0, acName1A01, SDO1A01}, + {0x1C00, OTYPE_ARRAY, 4, 0, acName1C00, SDO1C00}, + {0x1C12, OTYPE_ARRAY, 1, 0, acName1C12, SDO1C12}, + {0x1C13, OTYPE_ARRAY, 2, 0, acName1C13, SDO1C13}, + {0x2000, OTYPE_VAR, 0, 0, acName2000, SDO2000}, + {0x6000, OTYPE_ARRAY, 12, 0, acName6000, SDO6000}, + {0x6001, OTYPE_VAR, 0, 0, acName6001, SDO6001}, + {0x7000, OTYPE_ARRAY, 4, 0, acName7000, SDO7000}, + {0xffff, 0xff, 0xff, 0xff, NULL, NULL} +}; diff --git a/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes-esi/utypes.h b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes-esi/utypes.h new file mode 100755 index 0000000..0590ed8 --- /dev/null +++ b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes-esi/utypes.h @@ -0,0 +1,30 @@ +#ifndef __UTYPES_H__ +#define __UTYPES_H__ + +#include "cc.h" + +/* Object dictionary storage */ + +typedef struct +{ + /* Identity */ + + uint32_t serial; + + /* Inputs */ + + uint8_t Input[12]; + float Velocity; + + /* Outputs */ + + uint8_t Output[4]; + + /* Parameters */ + + float VelocityScale; +} _Objects; + +extern _Objects Obj; + +#endif /* __UTYPES_H__ */ diff --git a/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/CMakeLists.txt b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/CMakeLists.txt new file mode 100755 index 0000000..d39dfd9 --- /dev/null +++ b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/CMakeLists.txt @@ -0,0 +1,29 @@ + +# NOTE: add headers to make them show up in an IDE +add_library (soes + esc.c + esc.h + esc_coe.c + esc_coe.h + esc_foe.c + esc_foe.h + esc_eoe.c + esc_eoe.h + esc_eep.c + esc_eep.h + ecat_slv.c + ecat_slv.h + options.h + ${HAL_SOURCES} + ) + +include_directories(${HAL_INCLUDES}) + +install (TARGETS soes DESTINATION bin) +install (FILES + esc.h + esc_coe.h + esc_foe.h + esc_eoe.h + esc_eep.h + DESTINATION include) diff --git a/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/Doxyfile b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/Doxyfile new file mode 100755 index 0000000..3c634b2 --- /dev/null +++ b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/Doxyfile @@ -0,0 +1,1742 @@ +# Doxyfile 1.7.3 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = SOES + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = v1.0.0 + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer +# a quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify an logo or icon that is +# included in the documentation. The maximum height of the logo should not +# exceed 55 pixels and the maximum width should not exceed 200 pixels. +# Doxygen will copy the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = doc + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, +# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English +# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, +# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, +# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = NO + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful if your file system +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given extension. +# Doxygen has a built-in mapping, but you can override or extend it using this +# tag. The format is ext=language, where ext is a file extension, and language +# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C, +# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make +# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C +# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions +# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also makes the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate getter +# and setter methods for a property. Setting this option to YES (the default) +# will make doxygen replace the get and set methods by a property in the +# documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the +# methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = YES + +# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to +# determine which symbols to keep in memory and which to flush to disk. +# When the cache is full, less often used symbols will be written to disk. +# For small to medium size projects (<1000 input files) the default value is +# probably good enough. For larger projects a too small cache size can cause +# doxygen to be busy swapping symbols to and from disk most of the time +# causing a significant performance penalty. +# If the system has enough physical memory increasing the cache will improve the +# performance by keeping more symbols in memory. Note that the value works on +# a logarithmic scale so increasing the size by one will roughly double the +# memory usage. The cache size is given by this formula: +# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols + +SYMBOL_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = YES + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespaces are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen +# will list include files with double quotes in the documentation +# rather than with sharp brackets. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen +# will sort the (brief and detailed) documentation of class members so that +# constructors and destructors are listed first. If set to NO (the default) +# the constructors will appear in the respective orders defined by +# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. +# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO +# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to +# do proper type resolution of all parameters of a function it will reject a +# match between the prototype and the implementation of a member function even +# if there is only one candidate or it is obvious which candidate to choose +# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen +# will still accept a match between prototype and implementation in such cases. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or macro consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and macros in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# If the sources in your project are distributed over multiple directories +# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy +# in the documentation. The default is NO. + +SHOW_DIRECTORIES = NO + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. The create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. +# You can optionally specify a file name after the option, if omitted +# DoxygenLayout.xml will be used as the name of the layout file. + +LAYOUT_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# The WARN_NO_PARAMDOC option can be enabled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = . \ + ../os_al/rtl_slavedemo \ + ../os_hw/rtl_spi \ + doc/tutorial.txt \ + doc/soes.dox \ + + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh +# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py +# *.f90 *.f *.for *.vhd *.vhdl + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.d \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.idl \ + *.odl \ + *.cs \ + *.php \ + *.php3 \ + *.inc \ + *.m \ + *.mm \ + *.dox \ + *.py \ + *.f90 \ + *.f \ + *.vhd \ + *.vhdl \ + *.C \ + *.CC \ + *.C++ \ + *.II \ + *.I++ \ + *.H \ + *.HH \ + *.H++ \ + *.CS \ + *.PHP \ + *.PHP3 \ + *.M \ + *.MM \ + *.PY \ + *.F90 \ + *.F \ + *.VHD \ + *.VHDL \ + *.inc + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = doc/images + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty or if +# non of the patterns match the file name, INPUT_FILTER is applied. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) +# and it is also possible to disable source filtering for a specific pattern +# using *.ext= (so without naming a filter). This option only has effect when +# FILTER_SOURCE_FILES is enabled. + +FILTER_SOURCE_PATTERNS = + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. Otherwise they will link to the documentation. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = NO + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. +# Doxygen will adjust the colors in the stylesheet and background images +# according to this color. Hue is specified as an angle on a colorwheel, +# see http://en.wikipedia.org/wiki/Hue for more information. +# For instance the value 0 represents red, 60 is yellow, 120 is green, +# 180 is cyan, 240 is blue, 300 purple, and 360 is red again. +# The allowed range is 0 to 359. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of +# the colors in the HTML output. For a value of 0 the output will use +# grayscales only. A value of 255 will produce the most vivid colors. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to +# the luminance component of the colors in the HTML output. Values below +# 100 gradually make the output lighter, whereas values above 100 make +# the output darker. The value divided by 100 is the actual gamma applied, +# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, +# and 100 does not change the gamma. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting +# this to NO can help when comparing the output of multiple runs. + +HTML_TIMESTAMP = YES + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. For this to work a browser that supports +# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox +# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). + +HTML_DYNAMIC_SECTIONS = NO + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated +# that can be used as input for Qt's qhelpgenerator to generate a +# Qt Compressed Help (.qch) of the generated HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = doc + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to +# add. For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see +# +# Qt Help Project / Custom Filters. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's +# filter section matches. +# +# Qt Help Project / Filter Attributes. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files +# will be generated, which together with the HTML files, form an Eclipse help +# plugin. To install this plugin and make it available under the help contents +# menu in Eclipse, the contents of the directory containing the HTML and XML +# files needs to be copied into the plugins directory of eclipse. The name of +# the directory within the plugins directory should be the same as +# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before +# the help appears. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have +# this name. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [0,1..20]) +# that doxygen will group on one line in the generated HTML documentation. +# Note that a value of 0 will completely suppress the enum values from +# appearing in the overview section. + +ENUM_VALUES_PER_LINE = 4 + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to YES, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). +# Windows users are probably better off using the HTML help feature. + +GENERATE_TREEVIEW = NO + +# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories, +# and Class Hierarchy pages using a tree view instead of an ordered list. + +USE_INLINE_TREES = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open +# links to external symbols imported via tag files in a separate window. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are +# not supported properly for IE 6.0, but are supported on all modern browsers. +# Note that when changing this option you need to delete any form_*.png files +# in the HTML output before the changes have effect. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax +# (see http://www.mathjax.org) which uses client side Javascript for the +# rendering instead of using prerendered bitmaps. Use this if you do not +# have LaTeX installed or if you want to formulas look prettier in the HTML +# output. When enabled you also need to install MathJax separately and +# configure the path to it using the MATHJAX_RELPATH option. + +USE_MATHJAX = NO + +# When MathJax is enabled you need to specify the location relative to the +# HTML output directory using the MATHJAX_RELPATH option. The destination +# directory should contain the MathJax.js script. For instance, if the mathjax +# directory is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the +# mathjax.org site, so you can quickly see the result without installing +# MathJax, but it is strongly recommended to install a local copy of MathJax +# before deployment. + +MATHJAX_RELPATH = http://www.mathjax.org/mathjax + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box +# for the HTML output. The underlying search engine uses javascript +# and DHTML and should work on any modern browser. Note that when using +# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets +# (GENERATE_DOCSET) there is already a search function so this one should +# typically be disabled. For large projects the javascript based search engine +# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. + +SEARCHENGINE = NO + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a PHP enabled web server instead of at the web client +# using Javascript. Doxygen will generate the search PHP script and index +# file to put on the web server. The advantage of the server +# based approach is that it scales better to large projects and allows +# full text search. The disadvantages are that it is more difficult to setup +# and does not have live searching capabilities. + +SERVER_BASED_SEARCH = NO + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. +# Note that when enabling USE_PDFLATEX this option is only used for +# generating bitmaps for formulas in the HTML output, but not in the +# Makefile that is written to the output directory. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +# If LATEX_SOURCE_CODE is set to YES then doxygen will include +# source code with syntax highlighting in the LaTeX output. +# Note that which sources are shown also depends on other settings +# such as SOURCE_BROWSER. + +LATEX_SOURCE_CODE = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. This is useful +# if you want to understand what is going on. On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition that +# overrules the definition found in the source code. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all references to function-like macros +# that are alone on a line, have an all uppercase name, and do not end with a +# semicolon, because these will confuse the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option also works with HAVE_DOT disabled, but it is recommended to +# install and use dot, since it yields more powerful graphs. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = NO + +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is +# allowed to run in parallel. When set to 0 (the default) doxygen will +# base this on the number of processors available in the system. You can set it +# explicitly to a value larger than 0 to get control over the balance +# between CPU load and processing speed. + +DOT_NUM_THREADS = 0 + +# By default doxygen will write a font called Helvetica to the output +# directory and reference it in all dot files that doxygen generates. +# When you want a differently looking font you can specify the font name +# using DOT_FONTNAME. You need to make sure dot is able to find the font, +# which can be done by putting it in a standard location or by setting the +# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory +# containing the font. + +DOT_FONTNAME = FreeSans + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the output directory to look for the +# FreeSans.ttf font (which doxygen will put there itself). If you specify a +# different font using DOT_FONTNAME you can set the path where dot +# can find it using this tag. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will generate a graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, svg, gif or svg. +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the +# \mscfile command). + +MSCFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 1000 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = YES + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES diff --git a/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/cc.h b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/cc.h new file mode 100755 index 0000000..b3067b0 --- /dev/null +++ b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/cc.h @@ -0,0 +1,88 @@ +/* + * Licensed under the GNU General Public License version 2 with exceptions. See + * LICENSE file in the project root for full license information + */ + +#ifndef CC_H +#define CC_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include +#include +#include +#include +#ifdef __linux__ + #include +#else + #include +#endif + +#ifndef MIN +#define MIN(a,b) (((a)<(b))?(a):(b)) +#endif + +#ifndef MAX +#define MAX(a,b) (((a)>(b))?(a):(b)) +#endif + +#define CC_PACKED_BEGIN +#define CC_PACKED_END +#define CC_PACKED __attribute__((packed)) + +#ifdef __rtk__ +#define CC_ASSERT(exp) ASSERT (exp) +#else +#define CC_ASSERT(exp) assert (exp) +#endif +#define CC_STATIC_ASSERT(exp) _Static_assert (exp, "") + +#define CC_DEPRECATED __attribute__((deprecated)) + +#define CC_SWAP32(x) __builtin_bswap32 (x) +#define CC_SWAP16(x) __builtin_bswap16 (x) + +#define CC_ATOMIC_SET(var,val) __atomic_store_n(&var,val,__ATOMIC_SEQ_CST) +#define CC_ATOMIC_GET(var) __atomic_load_n(&var,__ATOMIC_SEQ_CST) +#define CC_ATOMIC_ADD(var,val) __atomic_add_fetch(&var,val,__ATOMIC_SEQ_CST) +#define CC_ATOMIC_SUB(var,val) __atomic_sub_fetch(&var,val,__ATOMIC_SEQ_CST) +#define CC_ATOMIC_AND(var,val) __atomic_and_fetch(&var,val,__ATOMIC_SEQ_CST) +#define CC_ATOMIC_OR(var,val) __atomic_or_fetch(&var,val,__ATOMIC_SEQ_CST) + +#if BYTE_ORDER == BIG_ENDIAN +#define htoes(x) CC_SWAP16 (x) +#define htoel(x) CC_SWAP32 (x) +#else +#define htoes(x) (x) +#define htoel(x) (x) +#endif + +#define etohs(x) htoes (x) +#define etohl(x) htoel (x) + +#if BYTE_ORDER == LITTLE_ENDIAN +#define EC_LITTLE_ENDIAN +#else +#define EC_BIG_ENDIAN +#endif + +#ifdef ESC_DEBUG +#ifdef __rtk__ +#include +#define DPRINT(...) rprintp ("soes: "__VA_ARGS__) +#else +#include +#define DPRINT(...) printf ("soes: "__VA_ARGS__) +#endif +#else +#define DPRINT(...) +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* CC_H */ diff --git a/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/doc/images/esi_pdo.png b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/doc/images/esi_pdo.png new file mode 100755 index 0000000000000000000000000000000000000000..1b21c3697583f4d130743f38d078a31dbf0f00c0 GIT binary patch literal 48677 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≺DYJ z5XM}{fPykfWRYo*e_arVkGkLms`%%}fxpGF|DP6vEvqHJVJn0AS<(J#s{9(JOD%q$ zOU3z$DK%F1yL)>mjdL-XDemjCsW5L^QC_`z5l_HwTiYuI!!KGi#XC)RTRJMZ1M zyW&pT1{~GeHH;5;7fTP*-xD7?o^bcpR#v(MG5)~Fm|^_k^@bxGgNti$CK)BsVkAp8 zZ@cf!x^4dsx(0#kzP^?FT)6y&$4RVH(>I1jtla#vb)|@RB2hit$7AE^a~m6**!PdI z5;?k(#kF`eL;omTxo;9^@yRKSJ>1BLM2k8Ujq@S3NWAQMyauw z0!pREl4(&QZVVdq2^)7gxNnW5%G4BK3iySFs^?Qb9$z*V*v_J6^_+2j9X80Ml-5*tIj`I@_r8iDQCeZLvbVd)Ys1tZ6IVc2|j zR+=*C+N=@Ll$>!*IX7t&u0FZs84{{}uB z@>jpW{$S2VH=Hj`Tx+?oSl&6$Y{@sA9~A5`@w8PC9ra)tZ2OZl#Cfy+ z^w*o$?$_7X)S<&GCth#11aQxHvsDviipM%Y^v@!+M_`kI;nJ?(R7E7QXZ4iVTHa(L za91Wzr9Q6pabT(>=3{&6)35P=`c>5>+4qP>$C_2)2^bVp>tE?cE&9uDqTGLQX%G93 z@~1a7$^4xQ67ArKdP&2JM{%V1sBK)JUOyH35~?bPLQ zrTe+eDw%I`vN-SO{D19>T!nXQ^AG3SJ0++Rm+PDTDp*wF&%QSxcJr@HT$33*_~kox z5Yq`f!@`2eWU;)c1V8TNl>ib)Y(3)U_@?*K1`u~6BzF$xNb1zt;t7aLj zR4tPM!I4nAnC;Cp(%lbNrO~74Pz$F&xqSRRm}WTwaam*Y2Ihn<1*BT`Fu6d$D(vO* z;^S`(cCbz7saqL@p}JnFQs9@O5O(p$mR^<)4EhXKfLucb`mm43KOzwv_MB<~M$8F@ zV7r*vYAax_{0pRB6sTe_39&_%5mTV^62`1=a@YatO0C%J$oY-uyfT7)X@9if_Qp_X zm^o#>(e=}+n&R44<>rU2>In=|LQB1)&9Cm)4l^|uTh$>NVs|1;OhF!#_4ET3Ht=dG zxlDg&>(+a|Z<_4$ejWn9Auo4Hv~#IR9IHOKjX`n)~o<`jgn|KZ2bZ=&i5=1-^j+)%$3O`u~b!3 zE|{mXLc`v?so)9pe6E2Z zn0!c!10>H$TP`eO92U3Zd2v1{yVpY_FDzIg=Dd`evmYJ*J-YfG=7qTVJ?BVMmG>*I zC5JDNsZrz0m_^0me8|qMT@F=5%*V-^F>|0Dka0dJ+38ai6B51*qOl#S){C0Xm$sx~ zD*5*II5iT{O+%n(8gqRGk`Zwk9zmKvEQDd^&c1<;G2))VWU6&H`a!1=!?KO+1VG{v zZcj^qrO25YE}tdqNK4i96ICmb^x;SE94-f=ktq?kGkrw@h2x#?*25opS$87+ADhNk7gK#dAe-_|+&qpn5Gsv4_hN_Th%h=fLJR$OHdO5Qm=LIsx zNFAqaK|wT8>?x&9REV~t0xJ#X-ZGEdqHjjk$MGxI^;qjldXnxEH_4Bm+bj@=kUx+L zBIvdg>$p+zx&{Faz_7*B`A0NDipJ#bC(Y!G88g1!mm4Ntu5jf_O6i=@5fFcbRSit0 zdCRw|ncfUgas5>mSZP9@T6SEwc5BIQP2h?N)U?c`nIfn!ZOqjZ4wXoqMi+XZgn9^2 z(ddgbI__N%W4qOw|HOAoYdT5({u9;>o3v^NC~qsn!Tio~nESTjqo@2N;?YlIl;U4# z4uT4IzDRy^i{HHMC_3{CP!8AnY-dIIz2#0M!&9mDpV&1&i$R^vSZFyh@CB55KtJP~ z?C_`$W3qfMLn7#RcB_V?@ya%=s_lWfaxbp+qO2=wZfL5?=Uz6X6u(<935jgd$Gs5_wQIr*5A_m`v3M zFB3Fr&BSDa@ne2)e1NLNZQcdnqkmHC_OeXSBHbo8qhDjnVV074-Fe96NQ6G zN%*@!tc~+T$D&~MrST5;dsncZyoZXmZ{12&wHC4i=J;Z|o-8&Z5_jB?5 zC*!}E|6GAY;t>zS;(I}fCkVL8N^6y~`FrOYE^|u&COt{AoFgx_U8W>Hj-(agMbu{` z-_A3UKvfZdIY5ldz73Q|v!2VE-5V^gSHdXaT)_}r{T%{%J>zad8kF_i6rlfs2n(vt zo{S=+7jjO<>cT@1AGRZaR@L!Mp;zv`4i28tl#`aMjo9>SW91O&TL7A7>YZ_v1TOwVD%gAWYJb*Faw?26*y5o5}oDz?LnmJvo zBZvNE?R)fO?m48tYVU}s=8=GYLrvQkInQLuMOAE7oZTbMx^tSMW7uKW!ZT#P&3vi| zl*lLX9U+eu4<|H9&hd8b`*rCwZ}+4ZR%1GuZpruO>YeSNs&K1G)S5+2>K1x)0MB)f zn_kHWZyzxltUUpdW$OYZF283r?mtM7{AXL^!Q)FL@lXkiHmPRjhlYcR^};kBo%a`I z8M~2JL$4XX{o(7YKiX59I`~R&u9+W?4GYHEm_OWTT%rzF98rmoIGa0+o{o2>F5JB@ zHB0Wb9loFD&~q|gsgQ0~yHQ6cv=TlK-SnvDXnCSHWY$d^h(npT94q2EI-M=|R(7pS z*KtkQ-tRDfx^vA(9dfsEUfI@wJWfN(M4AH3V``MxXG;ZmP)*HE{(}&Cd}f%RzWoFi zES)X&BCeb=-#k?iI>sSP%L)6TtNgv?qZQCX;5VU>Sop%{Z}QaFH@nSDV?LK(I5>R= zPW=bZX^Sn#7Iron!iw+2!m~0D+ne8eo9wik@b+hBU&G%!LTk7potcx=gmoopQq%ELDLDqL4LN~K~j z9l=7H5i_Z&1!ERBAs+#8I+Yq;b1>0~^JtrYX6LKtXJDc`2O64#qG4tMIu-nUd#QPa zVGiXhw-EUw58CxDb}99?#H>?(Rft5Hmk(#XN%It?C1B-w8nL|X2jkc)v;j|%Bd1cy zzOV?yu-v|S?%=q49Eyb#c%q=d^420;T%E-lKon}_4`U`ByDEIW2kY(;1CIvV>JEJu zR8k$gt)LXUm;iz>V8LQ2wDElZG<`uu!+`Sm2V!QA|M9H88;X(Evi-{#+Yi4*rJi|? zPVN7o7s*HeGkVb-vOLDsfAbIX{004gVxCcXa?@ARr~jB4P5+u1|En1>5TV3emW_o8 zMcs}~UQYQ_tqo)tl*1{3%3W#DQSlg?0uuVzKmIEabd)LbKcOW3eh_?u7@Ca53?i_w zVPhW&BRCXsHi0aL|El_ta6cztKfOjq?)_TRXZ5k3wV_;FjWU&=2W@j^@vL}ByBL{` zA>sT6R6gKxk3iH3z@&4~@(F)kqc<-on75yX#<*UTMg0K%_-h}b9p0k^;OCBib;o~; zq8kJc3AAV+I8vMtr*3+)-lioo;cu?%*BmiEd^?Rp7qS1XCz1+~&9{i*aaq9fq>>v< zFX2AN(zI049FlTzXuS6fS^1W~+j^QD&bWhmbJU$9A9I6Q!n>*ObB%hTl z{@NIzLcQ$AGV}^2hI{Hv4&N&!aKUBsm49LlP*qjkMU%_XT<#1bev39$YQ)|EKGFSC zYScKKC6Oij&Ds_h=Ltbl!fRFV`oa?@&71SRj_|$NYQ@K^rkZcD$_L6Cezx|xtFs*gU@0&o@DBB_cmFF5d`1%QQpV5C_!0K!v zutGe4u?#y`islGM;N~lO8y5Y@W<>g>M&--~Wq4u+`J`x!HA~)@Vuo4@7P2f{^A3lOf7dkEpS?yip=2-BCq4ZSB z2KPNmf}|K)3GnvaQGkJvkdST#3S1vMQgYxHwV?*1e3kKeSZq+0(OiFWCpnNk(&Qk# z;qGPb_9ON>vNbACG3D-@AH(E@Tzf3eT$ipgHR35J3Tq=ODm5}Hcpbclbr30V`Ochs zuUix{@_h-EvJxys*;ph2lDHmCSb45@tU~xb_&$HzoW1VOPIbaK_nEn^dl3(x&xI0mD9$+)rYER`xE$u8ub;upfH(Iy2RpCA)B?l9 zabN0;jg8U6L>#sEQC4VE(^rlggA*tV_zMD(+hCsYo7IP!JPlSL2OnQTz__Wp92PGx zZ}}=NF79`ZyuDe})RI9ZG*&4haIh;Egs6{y1a!O`npFC@{=WEo)5>6|GI`dl(3y)z zmjzg?RU}(H!)UHF9`OlVHGR0Ch`4#a@l`|%UJQGPc7!J8ks7jXfwi9r@AsC~LhMPu z!SCk_JflrJ5<+x#WeZ2U7})iUpl^-Rjb=XDI)5@7LhCc)cDzLe=GdO-DmTSYms6gj zKJsWN^G{)31`Y~bnpobfl{7n{ti@(y~-UWZ@Bj&ZRC~d!mjczMpSpMSTVx ze(l$ZuFqHG__W*+QLcHYV)-5qmR}JRjt{!%ff2Y0mEIfBKLqvKp7uo@zo7L`PpyJf zc0#W|DX_4`eP1O4UhG{^pp9DtuEsInpDAnY7E_#QiHfX*jV^!bL2(b==@jHR|BxFN z|ApK@$-q`w$5J1^&_;Y{kf(*RG78>xFF!HABhB*FYHbf;w1iZj!+-*V$D-*g}?RZ_^~M@J}HMT+Uanw=mSBqa!T<(v$Twp22CG=p_2`xrgB2 znGv;-wH1_iwcbp&8&BqM1W(&E3v_J+;X?VuM`<6e!E})pHyr{7yQ9cYEgS6PM*b z4Ftm*H{nG+AC{o0%9#e)_T3G0+1ooR`5&|7wG%IKXkJujxX{V+EtFhtKgn_!v~oXT zIJ|f0+Wte;G!szSXEsr0rV23ftS9muFutJnmT|+91bnT=Lge>BFbNHl{l|zgNThWL zu`@LvHUbvixp=>ur-nSzq0^uMb=tbIuWZ`m?+oYx9 zG*5Mu=O9dq2gPl0oLB(<#*U}*PwbqgfQ`Jb_;;UzCmq!$g(Gtzi7Obkai@R!u2g!Z zM^qXZ5p4d~wYBzTByz#5_7@TPi z`@gKx9tS0_-pm41bfKStH30nKXC|VMbihzA%75(YV?kFtDCbN%LKpua#@*iQAQO)p zK`31F)FiS=|NP^@yY*+tUvGv^0V*}exjN_WnT~5+=?cXogBd6RCnA9h`{n=-SDD3| zCV#FBsF_R~aDUKxjz!a)o^uJKq$uH1j)_qAJ6rb~9vZ*7xWdVb7StnzDq4BRzkLP{XJj?Dg zgik3$Jurb$fs?+TyAlMd*E=gb?kgV!Z$k!h%5!kfrmY>boT8K6{cs4K_d=a&ro!j) z0oD0D1Ho^3UFWI~Vy|pS4zw(?Nrbbq>S@0}F|K1W5x?i z^W_2Q>8$-J3#`6yS`vB7)#B}LNcG=_Df(Z-q^KA5RPj{`4a$$GuWY;KkrBHZGnK_h zJONrP$n8a3R!3K>I*>|gWQ(6O7abJ zjiHlDPj2%XlM+!c*2QfHM2GX)D$u~&>MIkacU@h$)tjCq!U>b4uA%+5LFLz$)eYd3 zlmgwKfq@#pbl3U>B%3>V+Gl@;d27!DONd{MYlxXX~p2XjEQf4D#9f5 z*hL4`A?FCJ`Ho2&i?xoP*=_mYTg|9*eWzv28S$S#AtPO zhO`Vn0rl*w4#dJ6n0%(}Fnp(8faC&<0Ue_cXVHz)uNX{4Y@rZtjjkw+S;6F=wj=vB z19qZ!$$A3R+V!p4(S6AuiC|w}ZFgrDcAT=8P3!>d6b^PTucorsYKJQH7t)}f;p4rP zBUJlk1$ROxXep4=?Lb4FgS5q>j?Rc(0@_%l(g5l$ftb(yb@uauMU?~Hkik<)Q^3nY ztx$RbV6HYtr8S@!MMJ5R5kbSo$l5?FrqvB)UXq!ly;gnoyM)>#Bg7Zc7Nl-$Q-S_T zG?cbsy9GE44;PBcR@>vb;sm|smgT3=VVYX}YXnk!9=u2+#7Us+4b(6$s3t2t-{c@? z^H`HE9EbJ(7HHKADVFEsftr0Wj?f8vwbme%AvS@b8+E5G8OVZS8Wp;D9f7%er#0Om z`o9Aqr(055io6;c%H7i%AG_w%73orOagaQNUS?=!04gV6_S_K-l# zDDDO{JZMCq)`(82{3ZAz1;<#q;my8SHYF3vDtguDz1y^Cz?ZlggZ^KMhUTFP@TD(0 zOoj)#?d4xA&Tb1o# zkfrfDyUtn4h{?eERICt zi5P&wR6E?FV>%FF%x<9=CbIJ=N=M<}QZ)L%DH`|_3h$HTGXBN;(SKcTcEWAN3beAu zbuUs*R7PT<*=F*M|4N$3vHqI?hDt-Re|S;;lZHlq`Y&lH28wlH{`e&o@~_)_vH#z` zy}+`1dmOSVts46Q^3L5|z?jBorSr)cBE1LqVt2;nWMfE_l3!7?Cx*7?XM93}MY6~1 zPGyt?Y1WmKn;Z0oKp;r--4kMu{duC&(3($rGx4_YKU@I{E=R`8AnyzjrE!C}tZ7PM6$==Ee+UeC;F?Q{%0C#afOoI^0(AC zU?^{O9UDEsQ~{Won7Us1`RZDX?uk%WfjZG-VEmQ}9aNoI^7@Q#yc#PJ zs`7jQ`#3Zh(YcLl3ZOBccuR~u6-c_MxA|32%XX@QIN=j)uA-5`gR=Q9dZO<63>0_& zUdZjs#m_2hvnUo#JT|&>MddC~7e&fcOnH~O>8kL&Pfjf)4U_nJ^Cs3p0-L@O3M)PD zW3>Pj(-#hvgCA?X4zqGG1spY?Hn$LR!0;nFXw=)xB7gh=msQMyWx=ze8FomuBc2uXIijR{ijm5v;w_ z1-Sjem>r>;u+FKU=H=yeoXIu)8k~cLkzR=Q{i|UTchi)EgBFqVkj{e2G^k*&R-)61 zudb55^?aYi-TBK16^mn*JVFN5+iJgof~AK`zPF<4IlsQI3{IbHI^?ZGq-kc>8@=XC z=xX_*f)!Rj*ylr)+Cj%v42!_R@zaED{A`Yg4ad^QXSaZw?4*5T?WZU&z~YMvN;jej zW?f~*;)G84w4U;ODH?E=BeTuOjU__dJ(S%nZUp0?9DgkCv6$Tafj{ciaAg za)bI1>l)jwP)S>FVGz1c4=y4sHUee6!hE`CUqv{4Yier7URk6V2;9kWS;yzKGD^3O zGVE!rwJEieIG5AEZ{+OU`H6mj=kVK@SQ9IZzslFkIRdv-+PJAgFkJQqn{KVCILQoUTwpZaG6P)G7jl+^ zb^p$QxJq!Z5jw2jJDyTDhD%fC={d+54 zrPs@fe@-6K+ge|sDgwDmVZ_H)aG?OcuhIOvzfNK;(buag!Lx{aW=O$mMx+!+dqS0| zEqMEiudp9ZS*oFiJ9m{#*68(0YTtl(XrBXRz|fbR2OB@eCy+x_#=oPiew1)tNDLlx zUZsy;)kYmVUr`Q$Q)x8=Qvf|e9wY2tw5p8rl`~c|H6Chr^m+97Kd~{cVSm|}?V zB4VY~&PE=xVs2qqmgYEXw>u@TmHobn7*|1ZSjo8Sh8Nt<-ND0$MZ>;;WJCi;T>*`I z(z>{Oq^dfhPd}r_1%Y(0{f*z#SPrOA+2rZLGA-}o-sl&5`3-^RAPiuF47ZC^?@6IJ z4ST53dgN<5c+IjB;tphRmWB%aO!viwb<1;!^AW?r zld~A2HK`O8#e_62hMoYOfAM20OfPKJGHAnII$M z@K4@8Lvx?s3WS6qQ0nD~4%7v9c(mTk?$?d);ZaemVH}BlS)@1Qj516pC*=w1oVShd z(DRS)T)r6nUZd0oQV3|Q5EfL5kKhx@A<(SwG2lZ-abn7Z&@nPe`#?lzj<7rjRqy-1 zV3MQ_B-&Z%#2je`Xd->(#O`l(ad%hxm(m*_iC15;HTaB|pbnWKTS>d#(8$bm%Z8`l z5XpMn2?TsH5uFIbzA{3477jSE%$ zPvN56{~z}MeG8ST0MqZxe`>M|*$jj*c@drL#V*Vrz64O1d3TI=h`Q%n3SQywC-jN( z#LrZ&7BzjybQ^L!`ek1`kzpM4B<*t+b%g#|>R}r*A9k@Bz-zYskEd)|)DiOMR8C{s z(XZbq3kbp@`y7wnSv$Np{&rqXU}Vp@_nWY=L$Vy?O>?a@R0u#T~1hl8^_K{zMAsqIYXEiY|bK zwX46m8ZApry^%H0d0@4@cUBisGPnCBEtR+aRC0RO%u_L>NH&X9@I+ZYY;FG1y>Q1v zlBl9cPf+JVD*4-FdH`b*JMMVtz4%krF9t4D+}fl4j7eMNV0v~-H~>!5$c=t*iE{1` z=!yN9$(?ee#&qnj#_B!mu?X|(!YU%0`a|hv@cgG?kf}-^>J7;Y9+ml>g(q~4aRYZF zUGs`90k2oKl4txlKXY#q((Ovk1C>!r)OoxUR3)u8X9{pCL>+B8HURa;|NERNV*bwh zV6$L9dg=zzn7~7~jCrpUV}7x0su6N8?&DaraVvPId^Aoh?#Y4QdUSM7SYOdtu%fqJ zY>7B%{NianUj2MeXjK{Tt(uimRsH^<+ z??z8i4i5eIo$(jS1Po46%*to%d|jYGsVwYxoMvuoJ`{LKV5+Ck=S|2`91bcgDGW7R7Rfvjt@^xs!24S~LT#z56kr+F8P z6(GJijU^^EbHRZgU{IkuI~7=y^Frzc7RuUI{C`pR)^Sm_ZM(mU3W9)wh=PCu(ukBu zBN9p@FqD8a4BZW)2ug@_gLKT$-74KVG=ntK-SN8yyyLm`-Ou~^J$vuZyZ^ZFzhuo? zYpz-AI?v;K9H-DDcf@<01QtNF*rVsG3r`cy$ zPFcH2;@_}#W}r_Mx7Qmrx+3rJItbG27v2$Df?UC!5waP5`q{T64%JP)*3D<7R()L= z`a1Q#pi~W&gzBAOW1%N{R##5j^ACS#ubFl~qpw1n*q&P&X7OrC*+O;yg#ZisFeG-S zfO?+Z=6@)l$k6eshOrqRTsO9@9OmPN6qU7*T(PN8^6Mt@l{sXRZPgB zv5tEX8<(Ywo49(^(MS;=}k{-LyW%d)9byx|c}q0mP@Cn*ngamH8M z)1P$f;;@E(lhcxPCOlgBIcA-_Ke9S8@ zu(7dQX}ZHRsQnj#C7~Z{(!uw3CRTz;3Z^B*FPYRon*02`ZXgePtELg9!om?knPd7YnJtJjVqi-uOoA?(g z^#(;sL0=k-*;trpJKsRjp*iEg`}GU9*@6yxC$^r&X#K_48n-6wFCqwlp0kKts6=2Y z*;<}}>F^4x5|udb<6MgQsXnhKMXVC&s*?cTStJyhf#p4KmoCz}$uXA~NxrdJ0u;be zlFM@g9%pz{M*6!ZYZJdSx^(ilbSXa^66oe4tJrJSQb{hX*LXuUwJtm<548MeyCmA0 zq)s<<$6iKaNIEHOB6meI6=|-BuG%b8Kc&o$6+48{?OqqdG9bwg^^wJQ>08d5<^0^B zP%;0zRr3KMwoXCFt)(Px@8}q4vZE{89Vh~Sv?vqlS-YOiR3IwVqGetb82GJLZLCQD z(XIi9aRaX;N4QiSQLf5Zn*$pH2UmiGO2Tx zQC()bX2OYLg|ZtGKac96kF*9iCF6eU!^sznNg;K~uoH}KV!N4f1_}>c_}z*lJKZ|j zJx%L3D-IFIAj*pzUe~2qGbNFLj=8yZ@}>*k`*2uC&@)&T)$Cy8T`{L|aCd5b1K}yx z>^rAevwiwvwv@T~LFV+u{M?ssrddb*VbwJ{IV?$I!5E8c3CfR2nk%s=8kN6My=l^>cHqooXEBI8D zPw?U|tgWrtOLMxpxt*~Bb6whq426maR=KAr7M_xlGW--3Abjxhi}yGO{z=N$gin~! zoK}~)mnZ6c1aEfRnyd^L$qol_Z(|FjJoV2NcSlqz@0=0J(K>Ty!-C1?D*e_0G3O}{fd1N z{1Vn(3WVdG?|z zsCtU@csYtzlWEuK35eq`cK0NaWAixw6gRznr{dPQyw-yLqw{A**PZ-rjK==@t+F!z z=i(#-OvlY;2+4}qh-Vl4Ng;_$Z-#&jAZ#5d^C%dUviPM;uMu;)g+R98dz98vfmKJ4 zk(xAbGqMlh;i1hb#Bf+A?$^?I(JR5mHKb+;$n3kn%kn|MEj23uRvRcGj~hq4LF zM1kIf@GM2&sX7HVz?E=FKw%|QUbORMOrMqf#{AF)JO8~|g1F=7S58K;#vK-j_sA*w|A#u%(T-eub)EGG=4g@=K5_GP?+YCQE4u{_(vhlG}g$MZf=9wA$x z|E3jpJQfu%uX>AKYFj(DsbWzcTvwP8*dqS&THZXbWrgC26p15tZGU>bKva<(dVj{s z=2wWs4+x04ay1#kAY(k|_ei218pI^RxZurm$ey#!x9o)8P^Z}O5e<^aEHkIg9*JrM z5*?kM_FII|*=b`Z_ErbLs&jSpX$QB+Snam5(_ix6*r~(l+cwygw1B z1Tkty#*~KdEY5gLxn$Vym>KRZQ2DK{9rhTaq_`vL^^T83N-!5f?hd4dVZsDg4pB^i zpj!2ijyHMQQV!7;INQzl9*Ej`aFZjI{g(C}qg*yh#@2t-IN836$L8$d3vRy_vFu!! z;eRrt+ag~RYonlB+8{#Cwkq%>IS_G2k&i}^_oJ>wJ~s_@T6Hz&Fwxd%Y;r7);>IC7 ztV|>Gw$oJ@CxbX-(5M}Q4EtON3gu=dQqDZSmqDw@Q?`1bH1d3=Vn)*;vG5YX$q0AZj|M~axBHJX(r{ezHQJzP z+fx|_lYCUI#hPPV3c_FCTHp>NI3mfL$+9d9WU|prx=CxGk+P4`z;e7mQ=lmZfzeCv z)m(M2}27lMTc}RAbrX1A+#ykB^wm=a~nn^op3)KC^bp7?V+vAgasXZtxJL_bd#+cx!g4(;B&~ZBh^k8Y+EiI=$LPblYc=KkZd!-uEwe zr*9eLZ(A#6-llNoISw9r$OuD(@PGRVY-46G^uzpGw;8`cgX^zk!bP56EM$Zvs#x=p z-o2h1dCLXV4d5RmT%YMtAMC^)HLNu{r5j2IYPavU)wet|;yp?&LoU z)+`(j_syq;nN22Nq0Vd(!_S?Zm%Zx}$lnazI)$P; z{7<3ibCAR^#OkEn9=Yon4=(X^!UZbj%{$ficr;35RJJx?{9LSLLnqeN6$z`00V2mQW0r%%zerB4&+O|Et!)M`cVuq=YeXdTH&HZ!WNf*7 zmC;%1h1>-vv$j{VpmJ%Z(WDoibY@RcXA_c9)n@k=o}E08M*#cK0aRD{{K)ky8GA43 zf_TI`TIn4$24BS$&aZPYX}IlX92U%MEPRVzJPgFW)A2rI|8rwYyOa7y4(*=j(TnW5 z9{wV)xrl-EGtZXxmNn&>vmh=kpf}W$N6&_o2K!ABgv3CVZN7_o1 z3GypKaX`=76})@oyW7$JM!=zns(-%4|7UFj!Vno_JXf!5?%+NSSWOtGx}$-aMY;~e zzU?*D3hHG=hu*O#`f(LKe8|^wo$_H+aX38Y${8atSgHIL-H*nUxSOpf7Do}zPaWF! zoK69<-CxDb%{;&b*}2(cJv$1L-(A=5UWh%Kg&)!OhYD7hokwo@w_^{Ny^i2RN&Y0 zrZ-<@lfQ|d8~Q49TWyiYsUi7;_d*%mlS`6c?Td_3+d(k@$aNyTMhV>m6?%T`evNop%p<4$ zq7AmXcGKXy`-RIb88>G22JQ~KqjBABsN)F7DXLGu4&%&nmdR>{+~g=n%P)BnhJcO< zNg4U?n|5fu{wTQ{bi1z+p(uy-q^7SI1PXvvrG4#}@9o2D1fWh_Ed-Yj)~oueK16Ew zYqL|O+hHr2EKa{@+nt;+H$JX%(J#vPASuo^~xD zwAh;h1`~ceTVIs}Dl3`hA0F67d|ng#a7GKr0D1Z!Z5u5>nyh=l$~BdOc>JSv`>jmq zJaatZkLVVYJ!_lgAdeGB7h;|D1XIcR?F$Iv~9kN#c@K=!rSjX4}Up9e$oT<2j_n&vwNQ?GI`voodSC$}-mM3>y1evG)mI_L>i? zKdI@L|9O{jf4$3ZQFpn2;`uQ~sVT(NtnU*@r7fQqJ5HTv@*W=M8BQdHEmvk|GD;60 zW2-Zk@m5x3ZTAFanqriiIlUo#Ula}K8vVsDiJ#l~qs8bm;j~O2w}=tv2lrpPW`SEI2eyow90sq9kJv3Z6L{V{1x;50|V>9{nylo|Gw#wEy?W~K~md8JfJc3_} zJn&{M;i}qR7@imznc&b+wE+1+9oasVgAVt}hH5)g>w(i6v?u(jfB8#RGEg7Nd?_h9 z^YBYw{N-Q#hXv&S$v+REQa1VW`n-791IahQsXy7^AIIlXb_3M#jqUCAPR%ioEd)Jm zW;2_4owi3R99U7kfzGwb28*<*sj0ITg@uKMQtsTG!C7xW=38wHx7{C<|6g$c>2@o~ zc@wGelfQ!AQTgpD@&dT9ao55+AHvDF$Db~~c(pi?3t*NDx6$+8(4k=Hq#rf&yU~z8gIEVBN@~$#6W;?<;qv@arKM2^0(&G<-ZT)Y7bb%$H(7& ze+_dHSX-~r&A4R~hZ}3>nv?}N86}l6%*QhcuFx2{EyQYVaS6&|r^zmsa zG8ulKU)<#@woXGw2Tk1RI4W9$h_uZlXCd70*7dpju_s-Xore=@*XfB{B07?2482G~XzONsHM zuk8FSiu#rJ?JtgU6rTmXowXKjbO7f2{A?WInYHT}*u0%|aH=pqNim5g2Ix_k^2WH? zC&Z12dvckwvp>G#Vk(5{bUa|N6HaSrzD0YSq)cE~X+#=%5mO@kU$C0__cZNQg=U@< zf;bz3v4!=xWF|{LUa>a|#YZ6w2wQlRC2xQ`mf;OZy+zDK5NPnHLEg*#FN6FQPwIaX zG2berG08Oe*TpqFUWTIFPO$y&t4bR7tIPaF#=}mVjv2Pp?t=T@#a16^j4eIU{JK_psOFSb^(^!s@J*0jeJnq5Ky+%BzTACHZB2CBz2oI_ZMSE}Wvi9u zAaY@~ol8-Nb6cXQPn%MjpEF<;~PyT|`XAkdF4Gjvf^y+*3y)5oHT z2?GswbGmyRZZ+q|Hu?wN^p&~jarK8_`^qK6_+P4 zr15@NRU%jJZBe&j!jthuE1GvpoS%|OV}{ZE4>)nR_KUJJlaD@|aor2Eu?t`E?rCSV zGxPE*@n0pHeSz}H@20=lbhYhZ7dd(%|DzZ7mO}EQGpvQ+J8ZB3qB^khKvF~n&lH0w@g+p!?_g#wrdfq=Owx7YxR;z+wzLe#~H%m`){uvJE zrq#Fh-{tH!AFS+4OEnG|+3^gsQiTbbm=^;@#>`tH)>X?=bn4;Gl$;)4ndD_q+ z5h`A!HuFavk43s;>|?`Tyu$;n-YcVgrmkkCx$GcB<=KsR8v`UCuMyV!+Jsjr?;SE3 z@4gr9P+rPeYSnlEpy=-Ei^%PVeqZZ{5PD=&B@!%#zeXf=wLH{zB1JT4 zU&@6=uN_Ly=*!Skp>9DiBv?pVb~A@4Kq-^x$v&^PI>3_344(#PcN^-nlPk)so?C4N z##X1QuSZIkA&NKosw(V~J4(ivA8gZg3ZNZ=CjH@$^SfAo>L&-)KsS<-0k*XvDZj&SsFe95(Da$lDZM1Dj1`4c-*|NA zW83usBVfpO^IAXbCH`7%KOvQ8Dt>z%MELJ7t2AYx5)ry6ob^N@aS2Y6^FChTsv6g- zB7Gq_+5mI&tRUXlWfKeZ8Av8c@w_s8eI!Q-TqU0@(VizM1m9f=?l@Bi)sl_zf&`&j z)%3ngiu3_U9j-o}w?sH^nn{<5$9whr;zjc&3MN?r88mCbe^f>Ncna#QqJpuEWmF6G z-uymp|H`T^z0qI^I!(l2SWkrhUov64nL|n6)3uGZ=Hf(R&M7q%W>!LCb zdRN%6VeD!zV)0sZICm&MF7KNh4^*`#Wqoxe8kxNJkvQsME%egbaH<=(s~kL~I)L5X z9lr(Pq+P)W>9OlqIr47in#xd?yr015qEKj}_T-|h@~uSRX2LUUT{kVESM9iKfGi|i zn_n0|*uwV5v^s$+oL>2v9o!Sx!NF6I29-Ti{>=bFPb)Ftq5pnsm-c1hU|1k!-Z#QY zLNO+u8?@5gX&#tc4|Tgn+w9kB}f!YYl8I!#^!xZyN+W09VxrvZhkRn;T1JitWdXROdAX zoy}ZUjn>S0@2_8L!j`GlJzDVp{ma@TwH;Q?wUr)rAVi`&q=x1u2?^ zctz4m*;VTd_!#7`>jfj&F2O=>+&5_u$0%#%iOd)+O!*{~-B7J+tnaCVF>-xGMgsIP zxMU6V*!SyLL?QSe=6Tpcdh;Vo)HHcuAvBZ$)f2L}e~Bzes$VCLefemRNZEfh?6uwW zr`h{na8h4IlL=0}s}+Cq&+W{f`RB}fX9(Al1Rk`!ALK5?%DYuYpPnYP{hf)aLejn8 z%qv?+)N^t3t6Z8qP<|^bNePiAwqM#LB3|y_HC}ocZ86`KS_kkdN2l-{SR~DJMeIlJ_uhGlh*J9$tZsxB27mI8N$z+fsxb#QBtlIiETK135G5r-rTau@&@ILG3-XjBQa?F?0jfPQx*V|O4 z0)`6kO0@Df99K=*(H7BBXi6yW?-0dPXdx?5kl^q{M0WLGd{ZFN4yhax0vU6;t6hVB za)Zo{x_NWsh4yATda$=dS(x|XB4ISa`9#yF;XhAoNQb1EQCwFS3q)>T5#%e}y|>-B zRNGMMH^bYK+z~iAi7$8GPhW^ooV6$R`Mf{A%nh9s%Gx^WRB?nhr+xp>TzJwxKKiPT zBNG-I+j|l&g%C!U(7?}Qu03D-ODJ{Kg*9ht$}KE*URZ6mj?LT)wUPXk6S+^t`Xc9x z#fK6t?EcY5@&V%P*z?0>Z_LxnK5RsOLC*JtrdOlMFJSPYIGpVGpgkNeffMM)*-ZPG7LB{d+=UIW2o|Bz?r^w?+m zrX6riJxDok-Ed|(lyXc=482Gosx)~cCsKKVK56H)FE@EgN=QEXn`;E*`Mg0^(fY}K zgQf%v6zHC5yG*}S)&M@60Y;JtiT#^|XZaGKSN#7cUxH6bGSpEEk@rQYjdsgd(m~4l z^;p0`S=@8Hn(YIVp`7WCN#-B(HT>c_xVYIbNrP$TvZ|#4Or6{7^_Lj+D?T$&A}cu{s$Le%+Oz(HEq)76x3OJ1hlO$w%UR8BYMrXT7ItC3`} z4oXHnKMT8v2bP*&Sf=7+I912hn5FM*{4Wy*FBH!i?ou&rCt)z6HpNi}evX;%8#Qs2 zD$85)dFQK@9V*bQzRx$u79qVzqqNMNg3{@Y&yIfmpCJ|~QmXjf9{~$CaL0@J-`i83 zEPc}Dk1NDdD^QY*wY3kvL{zX7zh83c`R*OnUL(oq3?|9Y5}Vh$*&6J661FXRa%ZU6 zZ%H_l+c1LqY&+pm*N*`iZ8t`+*0C#Mse+Qfqmj%XAd+Oo?*vRv5Jx(LEeAUJl*4XB z26q;Xrkq*KRjP7BR@$?WyI}XA8Bmj|Q#pwrUd&T~IpmsYgZiTn)&6~hQ zIRKEaiodWcm=sd-@rYHR7yh*&I z-u?S{GeNGZY@793amhOo#zCW*4g_awuj{`!j54(-`BW9X@F*w541OLq?I~R>l0`i4 z)OI7HLFDe#+AP{uvbVk{&vl|Zs{=ZAM(Ss)N(59fza*g`K`jiuyHWK7ghZ$=yfLo! zX_O;W44xat?a;?U_{KK(;9x&BFzA%~Z=Ba&Kj9Ds@WMylMD`q}nvzq<3w zyDepfc4CA&+)FxTGhD)b5o=3bpI!W4`TX0j;eK7ZdkteI^*bFy+iwbkU=wy9(@^k> zF`=H%OR7P?p=$x@!oVB8QGOTY4VRO&-I@5A$_P-bU&9cKd=X-(f_P|1f-9u!THoey zh)4)vzLKl^O$5IUof=v`{Nq&4>TW6(X-y6LC z^4%X}$f8P!&@{fr`PZ7Qi7}@P#+Ok*Z%vzK)FG>rSli4nIPwqdx@))5MYP%%#tRksv{i2k}T`ROp zXL<|(-+xVAq)4w-fVdo5aT)_`SC5G(o+E@6+R99DH zEVQeJIBm}5LJ@Sx)`;|bcPJmrBS07(H&7)Vl1Z7Gni6xEvfXYhaDSuU9-WRXzj=)Nw?UEuR+#5t5wEeC zVUiIn_whkT(|&#Gifiog@qyVXbH&ZgZ8nhO&S9korFs1LmRB6dcMjIV?kwNw0|>;N=ei=}rUvxd$c~d`Y8v|BU1M(L??4;iuDM z($QghHTP$0oF0=gUEZ6ZW<}?$N!r-h_~j!#4b8Q@Pen$mgMk9okyp&I|4kSP@Unbn z(+vQGwn)+Ev!4A~Ug*EPIZ-6t6{1%&h9Ey3#FFOy@&f?%+UN6nv1bRM_!WZw5C6QY z16rtY>|(vwS)>u>%ztulA6B$j9S+}pNpB#3?e>Yvo!fV;A1t|G%bIR9>s$cCA~zmV zc8>9X7y9Otaaqfiw#2a!AZ>#UW~vCn8*sE_kN|2Sg^LO^fbiz4sNMysRt9mPGz~Za z*nlYn<`7rrCc>ddrk(l(*`p^J_Ym%N|5((&lzM}2W6i$si^$+o1jL{-KB4*Mz_kTb z{sU&y^6{c;AL;qMvOqTpmH98OYl^Ng(+T18ur6v$&||rf+v<;~B?n<-%zvR&3f@*d zCP4l;te8qHYf0J;nrd|x_3%6l>CZ5m5y556QGqSLgBctAui_AfabO?%fN*{ze<*cI zBl_Ap52eXDUH0rOiIrXpYjgK9x8xKC{^P?jwiX z*0vl3+pzoyD*r){8`i}b8j%-?A!No1r&338V=d6>CVo5NrPP;l#bYimF*w?o&xhG=7)agyb@;)P`gl))HS__}Jq~aE7 zi6G!qUeyd8+&rVP%=~)mQ@LZ`t3OTdC~C-~!3x)J%NbWk)=l}ywv>_Mx?#gB{SS~| zE!GhElmASvMA|Az7JwLl#85;AL4)T#wpY@o`A=iwUbrr}b4l%Tr$*l^i^NUmMprE$ zwvmUVgKpC-K9zQ3=FI3Qg7+Gc-5SBMXb&MX+%vu1RO%Tr)!XhQG_<;ms}jv}bn~K)D^&uI zHv9-CONn^|7s*%BYk~IqAU)4A6Tr=as;k|4tLU8N0#{#EKDf8ko0Vy-!jF~xU=<@6 z!o%;=m2P3Fa+DUW%bsak>VIX!B;3wfO%io7l19G3(moOAB+7C0xXnn0tN|w@uxbz} z9mx-|fiju;-O%^%I&8|CokJI;qF~T zsHe7X4SahGaf!)e-`LlASBaaq>62TArgED}ww_*)CjuH(`m7G1MToq2^MuzoKu0Fb zuzHEp@*WrGY@N@X;nKAn72$V3fam@nv{AW$JHp_)@C~>~rqWC+Nbf)vDM<#SP63Qd zLj}#(5fzdVd^&z^=kP>Wz4LA;f{vembO7Hb+;uv{QQSYWo15G9n4M0?WT$>#**3}5XZ5MW_^JcoJJK; zzS%^3qrCJE7O6+Haej=R#GjE&K4r|4W?M1;OV6{~AAnl(2CP(8-qgC5{>a-&7MrE7 zt0hy^{y}CM1J$-FW>_8!Rmx;1DJjda&CX}yIvl)n$JH88I`KHtEenC06mQoakx6dD zG$)|z376rJgGY|GqP-UD7FX2EWH!1_>T&w1Kz~3Gtw^n@l&JG}>5k z4gUav{g*dwf7ij$ayYZ%R4)wi&lD^f~;`Q%tscoHO!r5-rw?lR-2`D=f4--{r+wFo;{Pl zcsBI2Mw<@Ec1KCN4G&hp_*PCn9C&@2z*`zE@bPsl3(PVq6v4O>+9vP45j3vnBF!;! zh?d|fmM`08i%*{Av)&iZZeBy``Mp>Qdl5m&n!1Y6#}O3_RF~h_v(c2Yb#3zUM3-IP z4)$sLbfrB!L7E&B_5T~*${@ujf(!+y^36ZzhOJrnQx$*!Z#)RArmsMkA3&mO0)cIi z=k{Mgh|cUp7fmvOnea*m*nygym4M1MND z&NsJ+lm&#$LF+NU-7@!`wJ8F=0o4e)X+n-n+%_|7iIC5-y4z ze;#j`nzy>tJV9tHQdz8mp1rg{C*?i8%SDmHmFN=lhFKcH3cg=pOsoWnW{v+hY@!m*Y~EWn^|9Sn>#t zErziqCQIxG7bIcLms1c^Wv*?ookv+KMDTtoCBbu$kBVW=V}CbYlm%fSvvt_sb?zhv zmbxjJ*1t2Wqp<+;sB$~0^0}s|mk-!BAqpP39hS|0c5g1r4Kw4M*3C=56uf@w5GN;- zJK*i7JFjejeIb4yu?6yW^`v^E@^&2_rH!@Uk<3;$`9Waj;dw`|k0d93+h+Gx#kKKG z{(pO1LeBHJI+qF3&{<%7Ri6k#AZat7$8%n>eh+BFjc|1dhFz8afqM1kMP5(5&70|P z{SjvT_4tlU*eONYXS?B{Uw6ZRD38ticHKr}L#eRf^Wj%9(OyGjTQO7#*7%kFa_3zB z2_)=3Ltf1SAFDhTFYJVuyYGTSUp7|e0o|U}xx5Iw*~HIZdu`9sOhmYUcbkJ%Equ8S zq6gWqQ`a|5(5_jk*~+fRmz6OvX^q~+nkt!s&>oi^{+QuQt4tk>ajTNAlH})VIByf{ zv=lPW#dC3gyWX09bIIb8ac+G_&&B_uV*&0w4EBI?Nq5i!BJUy{x!6o3t(qhjccTwfL_bWO$+0g%S408gmMh|BGJ=D`0G0 znudOK8BTSj^t}nJ71`SGky~^$lRzv6i(6iwGK8EzExbi9{_;@jYJ=Jo>NYJLlxLM3 zsFxf0i9ZYGNA`|Qrz+_|h$*YU^ZdW~eR9H><+?DnvPX_mk7X`MotNEW*@If!-u^^H zjkv0dn$W;{gCb+)*CnzA#x$IKEs+KFB={*^#`)zt{tp2i*84EYI6gH@P)h3P>rYlL zN<}}u_pNLLG9An7WQweaK$VkoyK9q=0mlJtq;97oUaWTj7^()V< zF>7mUzt0+i@KP|z(Vt)$S$W`1KyS}x5Sn8C>AOs+!$yiSGUgfw0Uq8Pf|^PU!CUB2 z-)PMj2bdR2Iy+^?y$xDI?Usfv{pkq;s7*wQF-@T^=8c;-CmJqS%d6;#j3}0ulo$^e zNsjE?bzTaYl9iXIbe7e7A^7429^`6uNZXX_P^-yEl?yTV^6}Au$;?O9Es+q$D_}pK zG1vSJ%qhCdB`tgbrg_gD@hkxqs&Ida=U*~}{)^7AVIIgHR`3lx1Z6bW1rSi=@E>Lm z|M%iy{(ZAVwH<2tTubRoiY}J3UVB8f&eF16F^kTH{*uS6ECx~8D`2Wdfu}IR2!C!* zfB%Yrb!c>lSmO(6xXR6Nr8I510Ae?tS!7OwpP6DTKFN2xsVc7W#qsfwmbZ9VyYPT} zswpT-_mR(m+X;xtQ|7}8%O1lI{3K_bR`$(V`YTswpT9!;3oxAn4Uq?)wWY5mVhSoq9j&LxS`@+ilGw$sTm+VLOP-Rb*X#$M&i) zA$2^e;+A}uW?>+ZThAKl#VY7QhDwq|E8LA2l(ALuLo+_UpIB5q(0fci^^j@mcD75* z#dtw%SRn+CXVCo^%jD153#RhM93{_AXKjG^o9js;gAW^_e@)G3ZRwG2U+j0&GtZC+ zwViy~I4$)2h`8)A>$bB&?NPMvz=o#mk4v0u`e=tmyUSd+V#8raL}9vhUYC|uM=NYr zavSA&N0}L5%2(BxlYJh*pMP^%T&i%}l-YqX*Rjie{FUUvw<2DkJd0Gefp25zt@TW@ zE}B$A+HLwY3*T@@T`qhYdgR>qz}oN&^nPA#CK`j&QHFLsP3kpuo!@de`CH5C_wg9v zBG+IZrUc}~y1?zBq}L4AR*Ziq;U(CI{=dGTf5lu3-iwD;;A{7ER+FYT&|;aM4B^`x ztvL=QD?du>@0HySEy`M6=FPNVk_XsJ#3Gh@tb^f}zzDk@$Cn->1ExdKmHV@?>~nrE z@`eU#?qMy;@@uqp>a_1Et1VV@Tf7R6L|8`AiiSE`o8t1QFYxp(3|AF}ZojSi;qkI> z)G$d-^UzVTcS(QAR8V>T>xUv+Isc^EEUPZvLZ>HH?)^PFAX0i6TRAi7eR=^mXzll+mC{ZRT*It=H>T*Z*1o?V>q8KmApFZ+dBn z0Sc8QbUINZba9G#7#Z7h)9mVF;;{HwI!H9pE~6ltT`G;HByhhGTdF&VH?!Qxi*clR zcbg_iUqfZlqqDI_!c1ZrK~|oCqh5|bu*^1C^Vy=$USKQb!S^&J#;G*~*}SG?0Li18 zcFp4uQED4yNomfK)=m5%ZgQ_u>NpmKAaU~k(n;>l8fYGHQ#V4pOYiu-7H&GIFkaM; zm7uS$^e%&^w1X|9*gE-)tw`3wq^f>*gRRa{Mn5_UH4C?*6DR$gx3$$Jq^|0$vkrX_IVk?P!Mk#WQWw!w`=P8=vXXj zIvjY5yt%xrlXMIDUiqCaI{;ngpYH{31Z4&d3c8|uU%?kv1Aph4qom(a#B~8&##*{FbK%(?9%25QrUDi{8V^`9<#q97#Zd3 zEn9eLJ5L}A(a!DYR@MJ9KvR584Kyih7(PY}|C!`-poE)JI3Ij=jd+|2;S#-|cK#!$ z@+%V_1Rn-Gkh>T!5{<9rsZ^RcxX$~1*Wj^8RW43u_y9h7?XF>*bf;jHm+3WHtVkL^@7T`>P7HbMD+7fMS^S7|5p)7r`HlhhV>Q+j;5V#=BmK*7= zH0@}_Gn4x1uh7Z&_FXxVkAYdLUi1@@Zp}47S((KhFYQX^mQe(+r=ojK8tM^G4zk=2 zn;9Jcm_97sA(fwdCndVPJ`ZKux4RX^1`PBz-B>HN@rzz^RPQR*(v*3@=n>HewzWB- ztDBpY!^=(T+aii%<9De&`L>F3~q${Pf#2@$~9$zVDhl+PXz zDgPB{IY0<{%-w%JmU2}rpyyIp4)!v1&u6fJks#P#B=A0E92T=d)A8p^?OW`LVO?(Y zUSA^h*lnBTysl8Q%3phjPET!JKw8MJkO~v=0Yf4<9_nnVFi6i_22j*nA?F}bm1yYSJmKs#&FDQ z7m}zz?^C6Y#R%na>ozxi5cgh5(ie2VPG9Ak?-61*z_gIbwp%jUP1VcXHk|W%t%%Me zBkIWy&$s!uR&=Mqltj+?q#impD42Mmvtx&yFC=BP-JsXxy@BV?IR9?QAJaaKI|GDv zgkOqKeqk?)fgpbhQd$CdcU!Q`n8vq;$LYHQ?s2=kyoC?al!0=bbcx7TKtuml@gCG0 z&N?}q_~d}Ju>GbH6sOIi(#S$wG#^v#1H%BX7`2y1_=~ZMW$m~gj zmZGsZGmv?DO4!5_)g`73;u;cpWQE&;jx~GIT<{bkhlQt^J)GKbI&=VLUOP zICVSIAH#=K-DgKcuZSycJfpK7+E$x!jak4T=mjKEj)7Gl`01#9XY}$vfHAPMpcQ!f zSfqrfurY+`B~jKQy~QQk0)7nYn|OJ>LsL(9u6{-MYksiDZc?hlh05`@+bLbl&m)BA z%jj7Aos=J1H4!Pw##%r#GTV~m<~5bqqdJ>tf6AF31UP!rASv4Hx(ePKJ%JNilR-U{ zerVsGT`AQN0ej+XYna9pTv*|LCb9Bo?oTVLl(1#xx-@CxIJ(w^&K)vb(qu)>fm0xG^vDR3GOzRCr#gE1X6hp+ z?KXDvt=d~u6w%(AGn;DM*YUD#9?&%`_%)riLO4*Z5W_qWsQNFn9TjCD ztjcZiXWHG8& zx^D!M?4;OcuJayT2@H-nt6(o)y!fi&5;{7T^0VQ>OylRE!u#SGj;I2)EY~w~`OgUn z7x*9jZR7;5Yy7%iy972E>^Ls7x4#8DJ?Q-qv0SZ)h=|{3N}!S)jLJJ+U$?^nbB92e z;KQBYm^9ih2B$FK?f(zYln6)g&1&#{AA5XBEjYLzQtfaHZ*GOmoP1nZ;Vs7A3-hf{ zKq$}mAM5Ky1kAvW!fluhPD)c(R@qh3^C-6szK=(FH&Z3#ys)s@DC=<`;L_-KZN=fY zUfkv%B18mFkMiZ8bi^Fsi1IijBdb5ZaJA^!?>uCB<5_a8Ua|=atj^2PgZbmLWYs4Y zb^9cu3HMiE?@EMg9vP~ji}}&`=_I1|3t(oWME^~htg`Xh(N{P_%6^+_2CE{HLFbiF z%_I8abb!E$j>Bo%!5!>6O}Da$Rd5N|B3vU^hx4Mv2d^*!Js zi>~Xrp?i5^7LIQc^b|Ax?Cb51 zUy7i^WT`Mm;UeWqGfmJ0nV6clWAYG-oXByzd$=z!G(mf#6|TpuHF*d9s*%GMi7MFy zp-o|29`wCbHPI73kYYrOP1KFF5ibKZ-`2)^2el)9Z+^ArjPjoHU3Qq?Lyr( zEbc_)FiS{<8%G-Wzbp28%NkU59&RA9h&=5?fSACYV*l6?-iq*WgtU z7#1gW1H>`7=C%ePgc7)Lu{}Y2_;JdC!GtMxm}J$hLOA{UYX6s^gOU}JJA@j% z+)`nPtFx1>P=Z13{d=?6()|5ix*-!ISGk?yRueXtT;`QCdA9mZ(}w3oBd>SS?R9Bl z_*L$z43h1H{J7V9tp5J->|hG0N{TjzrfeHEe;n1(y{O6wGW7l>q#2I8!D;-Nn8$(yQ1YN z;d-lT&(iBrtB+`YisefsA9iGw#y0^!Z-f?~vzYpiG{N|xVvbqcA4TMQJ;Ne!y@zCq zkc04?&-n9`sYH!%*g>GvD;-hlX*jJKouE&v`RJhZ$WA&p8qE9Qwz5>|+Q)8v zA?ZSJmT_d+OYTk-Okwj5QLfL&7TS~02EG@qTQVU0H_gaQwyKmp=oi2~+Ruw|>n z2(Q3yI}@%aqj%W8MgR~0Oy5lzK;-iALx?ZNindmLUUxo zLaGGz0t~D2_ z<&7RjQ*L`NiyPNhfkFJP^L^*+jl&db=X!9bPleHg7+=0j12&9(S_cAk#|0aau~nLJ z?At+p7a)m$NSXJ5lxd@_^7?ZBdoRS*3M_|C!E@kgG@%cC$)X?#!<2YOa(*!{AZ8&u z4iVX=m^zhtzC6G1rcPflGJN=ovZTK;&*~qVN4T1bjB1vBVEE_f#nOz{LL}5A6%o5a z?mI_x;z~MR{VsZGR~1j3Y~h#I;TnYd3hT4ybXZr~MbA&D-VI7Yh3t6XK+2qXDG9aD)!-GXDTnlZh{^N}Gz(r4|x4ul-Pkt?#qhhre?qc{f z)mEs{FmmNbRFia%+=@E3`t%2WbG5LSefA%NEvY}s>TlV2{%BXMp-7VE*m<(Vd$fGG zk-ToQFI&;}Y__zYja$06s*-sW)5WR6!Y$z3T6@s*pd6yu8{u}w8H~s>(~*7r;@ByI zn>wS_1uN(rSc{2qiRwlBI5zS*L?#I`ZGf534TV$T0I$StP*b4J_S#!FaDQjMKr>vo zk*W$Vijui1tb5^|84f$UD`e<)-9(Mnv2#vO6ArJR&)qY1W(m>bx2*hf~V zIJcgX6{;Ora0elKch2X|GF;_LGb6ou0?thsRV%o>o0xe(#^KuuMj9F&&THd}tCIAZ zE6`}NmkGs#d4hy363QTfbfc5S+xzqj|Mb=joX}<-(O4B#fg+Hw_&!fZS+$@07(C;&gGSL~B$ z@FE5W7q@-_yD{S7Qn_P$Ik&JlEmbE-#hM+w3{MFg3p|eTL^l@)pPbGn?2jd3qZtxA zolR%}j!+nSQOU&N$zn+P$y{8wUZ**tWeV0csGC{g+V!gNsy<2tV!dM@k=jw)|xgx9$vSWIQgURXOjZ1j|sr!Nx+GRBqSt2Xxr{!KL0t3J))+qAla$J z<=~O+$kD;haJOVJvXV{}5488?UdrKtqYKTZKYPW_KnWsift>wcMAm`{kn8yE@QVWF4NXk>rWl}3^@ zpPurTeR7OSs-1bB?MW8jG{)O}UhQUTS{q@(>*MgZ022NG6?fKAQSE=fR}oNB1VKpw zX#t6$Ls}Z7yK5*xIu#H^K)R(wa>ya1yQN|1?(WXJ2R!vV&%Nue^*nc7|2b>94$Qb` z&;EYm{eHiI4I7|EhbMkd$u&1rgfow-{%pJ!&Rie-@WMCJuJ&i4RgXsiSFk!>qI{43 zZxBJ!cp1iV;>zagQk~%%++bbIG?=wU=lYZ*ef?3ba8xziF-S*m-rtL0wt*^lMD3JL zE~qsj7DU{So^m9_-n_tOrkE?ja-^t+qJSY)BD6{6Yqk*n2L}@gOL_@p1b7?lZn5kZ zibwEWl=&aKD4}P~l#&Mk?4Sw!5K>Z>!lL$hN3}o?>%kO$OKBCLZdjU*HhbISl6|fd)h_VZ^e<*>q|Qx$Iqn|*ZMtc>ey3>x+&1t?T!A8J8OLOL zj2!_x3Qj$6sU%#8O(`P!w&W)nF%pT*)rW#U7Ej$i@FFYlR3g#A;yY-qgpACOiTt-q zgR{ODQYh|fVJ0Hm$nMN!n$-v%ar?b%B3OH5LF5QPc$&abrMQ{34B-G7uSBdRKKoCkCD-H65M3v`mnq5WZ1-o`J`7yQ8vyeg1dTDaGs zBL8zJTNR%3*sMJ6(>da7`L~bxT5%86v4`E3Rl<)7F8}LQ(Q2;EdL5%Wz&N3~KUIW@~(D9q*|FqY4Dp{9dHN z+wr>aCc#)Cp17}9=dpr%c;tXnjFb^5D0$( z0SIxN92nc!cU~V$3XecuLTDZF;XW+TVd+~Etof=E8yViQfDlV3#s3y$W zuJvWYfeAlJc`vbAAZLQsqo3(`g5C&k577vrKR$^jv{}fB9Lbl)*AXrumnRf@cmG`}sUv4|CElSF-A&j0*z|LB#l6rCPSy&k}kR z1wuXFOD8$V>@$kP395lV1$k9OX-#~n_Y6GV3z(h=@aW+c#i0w9DtMF3QOc3}tNIWq zE>z6DrL-bkG;7VU6aFe0qXTsw_Oulo(yp8z-D3R{FRFmg8r~H#sJLi1W+jLPL=&I= z^zF#R$_3yW$v}e%8usC%1Tmf7rRI#PtMYFV#kDd>;1VaEdK(wHIuRlByjLJOxw z^!9q2p=ZYs@C_i9=fKbmmzqzyV>uj$rHyL&uRfLS68LN^X?M!M5v-=SYY)8qF^x;_ zV(xY=J-9E-v`)!z~eGSptnZXa^3xUvEKrj1GA|a-C~r+;IqOwmo?jN>4xyZCCzSyyfVA}#!ZRT7v&s9 z=ivC@b%+QA{uQc=Umc2QjZQ!O(iuWiDic7yFA2CK4vO%r;TOS;)vh@hEkEr6L+JQ-lOYpPhearJ~S9fi+ zMo&|cHkX_d1aJf9M#3_>AaddP<;Qxe(k_k}CftU+yVR8k>VIyuvc|N`QN#~is;tHx zjNgc+N^=9u)^;mkN2bJkbUIw~Q*0=_$JAFIbv|}6D4p<)P$(2ODgc7gvCo2xrtgE2S{^d5t#e1%qj6wbG^N4X>kALFYHG zqdiBtKl+OTf}e)EEK2)Z#y269h>U0Hsb_Ov5j-!>YbrwJ-eBJ|Hi%H5tdu7p8xg^D z3j7o+8^9B6xS|H@4dk_V2U_V$?CNt2_wa4?Nk71z1C0Z}$QicIPSn;dF;)$dDO9fm zcQQ6bvD)4E(sym?l%Q+JTCaJExaIZs?vhAjo4_2GhGwLRGcQL{S8d-aH*y!I`C0F@ z6v->z(rXENNUbV@LHR=mpkk`_0yN>=5SyngBP6uL~K{1Xzrr;N7=~9 z0_#K9mDdB}f9&(PzEOn#mM?WD1*Aw&5501OFP?&n<|=5@@ufH z6wGptV=axkO-lT8fUHb3+xyp+?9a=;y zv@DkPcFQM9aL48qnXBEqrPoQ}XGm(Cs(l^m7YEkX+C7~bckn>VT8;E5#`fwPrjd}a8Qnu7mwO!tt_Xv z;l%F>)*c;q_J{%dM9w$gGrWaQ)4}_EV2TDDWt!G9Xs1Dz0Iul%Ap>yk?tO`X?F_zM zy(}<@CMMwWtB!06PrKRQO$9?EQ7rSG-bhVf!K3~FMvth5P^%KHz#hRQyvmuwECIMG z0*g=R0gw3;8=PC8Zx(#l2Qbgdlri?$fl$)8 zz&7ceBtB%%lcT)6+;XC<&+z#G5=x8SFhnJPn-p+(p6)Ntw;BWCZ8xd!rGg$1h*3up#c$c_V_gt-0jV*F(9u3bIPR!VQqbZr0)P%G;wH~1V3&=eDcC$Qw_G+&CBdov_SOJH^ zSHS-XsEqy4A0Z-G`=5wwiEzL``6mh<{;U3fVkQrY)VnggxJ`~MPEK35Jv9qGT0>3S z&)3dkGEYN@OQ&o35I6YkE-rG5J!=Qo{Nw)tx!yVa3vylRt5Bzeqs9BwjxSY8$L1Sa z7K;jPo|U#ESH9rkc1QmK&~R-|EqWe|4XALP6A%7<8QABhKvP?0=5Bh0m->aW7MQXC zdW@rIq{omuoHT|YmuCG#_8WR(x6n$!Z+2mY+V3@+lSZV>WmS4-43|SP`4z#T&f_?h zydSCXEX)i4PeHlN_E59dh1W8C89%^NeLTKT!X__At$FY5(b*Gs{}DXDT#7a57t;=S z%$Y=lj#&2k@^6=bKXQbnS10L(#>pjz<-@5k8ri4x29sp@Xss*X+UN)J5c^BS=Q(Z&{L9`H#tR90w~S6Yg68 zM!vr#+s6YS00{$+cN;aVvj_-=CEIgR9M8|4@fOj_U>+_|%R7S8#G!h$InK^TzUXV! zyH~EFUOnaY200W~=vmabfX*lg$_QAQ4Mjs|WT}>8~ZAxn@8( zJOS<|a(>C^WDujxVd_u?-IwCmK30VGLeVz1=mUzSk%V>4zwA zs|IqVtrkK&5*|BTTwF#gJ%us+UO6T>|G9E}t?|do@gc_}z^nEUg2hYDQ{@}TE`SB; zV~pa2#=TKw=W76|yq(Ucv;t_7K&p93+A!r$Aq>fFPw>H0C7Q4E2AW#z2HAkCwImJe z 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 WDi#bxo;Tb9{)mgn2 RxPDO, Inputs -> TxPDO , MBoxState -> FMMU is used to poll Input Mailbox + - Sm (O), Description of SyncManager including start address and direction. + - MBoxOut Mailbox Data Master -> Slave + - MBoxIn Mailbox Data Slave -> Master + - Outputs Process Data Master -> Slave + - Inputs Process Data Slave -> master + - Sm ATT:DefaultSize="128" , Size + - Sm ATT:StartAddress="#x1000" , Start address + - Sm ATT:ControlByte="#x26" , Settings , Bit [1][0] = 10, Operation mode Mailbox, 00 Buffered 3. + - Sm ATT:Enable="1", Enabled + - Mailbox (O), Description of available mailbox protocols + - Mailbox ATT: DataLinkLayer="true", Support of Mailbox Data Link Layer is mandatory. + - CoE (O), Device support CoE + - CoE (O) ATT: SdoInfo="true" , SDO Information Service + - CoE (O) ATT: CompleteAccess="false" , SDO complete access not supported + - CoE (O) ATT: PdoUpload="true", PDO description uploaded from the slave's object dictionary and SyncManager length calculated based on the same + - Dc (O), describes synchronization modes supported by the device. + - OpMode (O), Definition of supported operation modes + - Name (M), Internal Handle of operation mode + - Desc (O(M)), description of operation mode, recommended, Free Run (no sync), SM Synchronous, DC Synchronous + - AssignActive (M), Value of Latch and Sync Control registers + - Eeprom (O, use is mandatory) + - Data (M) + Or + - ByteSize (M), Byte Size of connected EEPROM device + - ConfigData (M), First 7 words of EEPROM, Configuration Areas + - BootStrap (O), Start address and length of mailboxes for BootStrap +\endcode + +So to describe the application we use CoE and Object Dictionary. The mapping between +Object Dictionary and the User Application are done via local variables defined as +user types. The Object Dictionary itself is stored in a matrix where all +the indexes are listed. Every index then have a submatrix with its subindex. + +\section ObjectDictionary ObjectDictionary +The Object Dictionary used as example follow the CANopen DS301 ranges. + + - 0x0000 - 0x0FFF, Data Type Area + - 0x1000 - 0x1FFF, Communication Area + - 0x2000 - 0x5FFF, Manufacture specific area + - 0x6000 - 0x6FFF, Input area + - 0x7000 - 0x7FFF, Output area + - 0x8000 - 0x8FFF, Configuration area + - 0x9000 - 0x9FFF, Information area + - 0xA000 - 0xAFFF, Diagnosis Area + - 0xB000 - 0xBFFF, Service Transfer Area + - 0xC000 - 0xEFFF, Reserved Area + - 0xF000 - 0xFFFF, Device Area + +RxPDO , 0x1600 - 0x17FF +TxPDO , 0x1A00 - 0x1BFF + +Example, on how the the OD index are linked. +Top index, SyncManagers Communication Types. In index 0 the 0x04 indicates we have 4 +SyncManagers defined. Every SyncManager is assigned a type, in index 1-4, we have standard +settings SM0 = 1, SM1 = 2, SM2 = 3, SM3 = 4 from ETG 1000.6, 5.6.7.4. + - 0, Unused + - 1, MailBox Receive, master to slave + - 2, MailBox Send, slave to master + - 3, Processdata output, master to slave + - 4, Processdata input, slave to master + +\code +objectlist.h +FLASHSTORE _objectlist SDOobjects[] = +... + {0x1C00, OTYPE_ARRAY, 4, 0, &acName1C00[0], &SDO1C00[0]}, + +FLASHSTORE _objd SDO1C00[] = +{ {0x00, DTYPE_UNSIGNED8, 8, ATYPE_R, &acNameNOE[0], 0x04, nil}, + {0x01, DTYPE_UNSIGNED8, 8, ATYPE_R, &acName1C00_01[0], 0x01, nil}, + {0x02, DTYPE_UNSIGNED8, 8, ATYPE_R, &acName1C00_02[0], 0x02, nil}, + {0x03, DTYPE_UNSIGNED8, 8, ATYPE_R, &acName1C00_03[0], 0x03, nil}, + {0x04, DTYPE_UNSIGNED8, 8, ATYPE_R, &acName1C00_04[0], 0x04, nil} + +\endcode + +SyncManagers channels 0-31 are listed in SDO1C10-SDO1C2F. If we look +at SyncManager channel 2, we see. + + - Type 3, Processdata output, master to slave + +It got one RxPDO index 0x1600 connected, and the submatrix for 0x1600 link +one PDO object index 0x7000 subindex 1. The output object 0x70000108 give +you the index 0x7000, subindex 1 and PDO object length 1(byte). + +\code +objectlist.h +FLASHSTORE _objd SDO1C12[] = +{ {0x00, DTYPE_UNSIGNED8, 8, ATYPE_R, &acNameNOE[0], 0x01, nil}, + {0x01, DTYPE_UNSIGNED16, 16, ATYPE_R, &acNameMO[0], 0x1600, nil} +}; + +FLASHSTORE _objd SDO1600[] = +{ {0x00, DTYPE_UNSIGNED8, 8, ATYPE_R, &acNameNOE[0], 0x01, nil}, + {0x01, DTYPE_UNSIGNED32, 32, ATYPE_R, &acNameMO[0], 0x70000108, nil} +}; + +\endcode + +At PDO level we make the connection between the local application and +the object dictionary. For all subindex in the PDO the last element +is the address to the local variable. + +\code + +objectlist.h +FLASHSTORE _objd SDO7000[] = +{ {0x00, DTYPE_UNSIGNED8, 8, ATYPE_R, &acNameNOE[0], 0x01, nil}, + {0x01, DTYPE_UNSIGNED8, 8, ATYPE_RW, &acName7000_01[0], 0, &(Wb.LED)} +}; + +utypes.h +typedef struct +{ + uint8 LED; +} _Wbuffer; + +\endcode + +Beside SyncManager to PDO mapping we also have mandatory data as + +0x1000 Device Type +0x1018 Object Identity +0x10C0 SyncManager Communication Type, as we used as top index when +figuring out our PDOs in the Object Dictionary. + +For a complete description of the object dictionary you can get guidance +by the ETG1000.6 EcatAlProtocols + +A useful feature is the Asynchronous use of SDO parameters. In the example +we have Parameter set holding an encoder scale value, just for show we also +have a read only mirror value of the encoder scale. Parameters defined as a +RW SDO parameter and can be written/read by SDO Download or Upload. In +addition there is a ESC_objecthandler Hook on SDO Download where you can +add additional logic, ex. we execute the mirror of the encoder scale value +by assigning the encoder scale value to the mirror. + +\code + +objectlist.h +FLASHSTORE _objd SDO7100[] = +{ {0x00, DTYPE_UNSIGNED8, 8, ATYPE_R, &acNameNOE[0], 0x02, nil}, + {0x01, DTYPE_UNSIGNED32, 32, ATYPE_RW, &acName7100_01[0], 0, &(encoder_scale)}, + {0x02, DTYPE_UNSIGNED32, 32, ATYPE_R, &acName7100_02[0], 0, &(encoder_scale_mirror)} +}; + +soes.c +void ESC_objecthandler (uint16 index, uint8 subindex) +{ + switch (index) + { +... + case 0x7100: + { + switch (subindex) + { + case 0x01: + { + encoder_scale_mirror = encoder_scale; + break; + } + } + break; + } +... +} + +\endcode +This tutorial is just one way of doing it. +Enjoy and happy coding! + +Andreas Karlsson, rt-labs AB, www.rt-labs.com + */ diff --git a/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/ecat_slv.c b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/ecat_slv.c new file mode 100755 index 0000000..2ffce08 --- /dev/null +++ b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/ecat_slv.c @@ -0,0 +1,386 @@ +/* + * Licensed under the GNU General Public License version 2 with exceptions. See + * LICENSE file in the project root for full license information + */ +#include +#include "esc.h" +#include "esc_coe.h" +#include "esc_foe.h" +#include "esc_eoe.h" +#include "ecat_slv.h" + +#define IS_RXPDO(index) ((index) >= 0x1600 && (index) < 0x1800) +#define IS_TXPDO(index) ((index) >= 0x1A00 && (index) < 0x1C00) + +/* Global variables used by the stack */ +uint8_t MBX[MBXBUFFERS * MAX(MBXSIZE, MBXSIZEBOOT)]; +_MBXcontrol MBXcontrol[MBXBUFFERS]; +_SMmap SMmap2[MAX_MAPPINGS_SM2]; +_SMmap SMmap3[MAX_MAPPINGS_SM3]; +_ESCvar ESCvar; + +/* Private variables */ +static volatile int watchdog; + +#if MAX_MAPPINGS_SM2 > 0 +static uint8_t rxpdo[MAX_RXPDO_SIZE] __attribute__((aligned(8))); +#else +extern uint8_t *rxpdo; +#endif + +#if MAX_MAPPINGS_SM3 > 0 +static uint8_t txpdo[MAX_TXPDO_SIZE] __attribute__((aligned(8))); +#else +extern uint8_t *txpdo; +#endif + +/** Function to pre-qualify the incoming SDO download. + * + * @param[in] index = index of SDO download request to check + * @param[in] sub-index = sub-index of SDO download request to check + * @return SDO abort code, or 0 on success + */ +uint32_t ESC_download_pre_objecthandler(uint16_t index, + uint8_t subindex, + void *data, + size_t size, + uint16_t flags) +{ + if (IS_RXPDO(index) || + IS_TXPDO(index) || + index == RX_PDO_OBJIDX || + index == TX_PDO_OBJIDX) + { + uint8_t minSub = ((flags & COMPLETE_ACCESS_FLAG) == 0) ? 0 : 1; + if (subindex > minSub && COE_maxSub(index) != 0) + { + return ABORT_SUBINDEX0_NOT_ZERO; + } + } + + if (ESCvar.pre_object_download_hook) + { + return (ESCvar.pre_object_download_hook)(index, + subindex, + data, + size, + flags); + } + + return 0; +} + +/** Hook called from the slave stack SDO Download handler to act on + * user specified Index and Sub-index. + * + * @param[in] index = index of SDO download request to handle + * @param[in] sub-index = sub-index of SDO download request to handle + * @return SDO abort code, or 0 on success + */ +uint32_t ESC_download_post_objecthandler(uint16_t index, uint8_t subindex, uint16_t flags) +{ + if (ESCvar.post_object_download_hook != NULL) + { + return (ESCvar.post_object_download_hook)(index, subindex, flags); + } + + return 0; +} + +/** Function to pre-qualify the incoming SDO upload. + * + * @param[in] index = index of SDO upload request to handle + * @param[in] sub-index = sub-index of SDO upload request to handle + * @return SDO abort code, or 0 on success + */ +uint32_t ESC_upload_pre_objecthandler(uint16_t index, + uint8_t subindex, + void *data, + size_t size, + uint16_t flags) +{ + if (ESCvar.pre_object_upload_hook != NULL) + { + return (ESCvar.pre_object_upload_hook)(index, + subindex, + data, + size, + flags); + } + + return 0; +} + +/** Hook called from the slave stack SDO Upload handler to act on + * user specified Index and Sub-index. + * + * @param[in] index = index of SDO upload request to handle + * @param[in] sub-index = sub-index of SDO upload request to handle + * @return SDO abort code, or 0 on success + */ +uint32_t ESC_upload_post_objecthandler(uint16_t index, uint8_t subindex, uint16_t flags) +{ + if (ESCvar.post_object_upload_hook != NULL) + { + return (ESCvar.post_object_upload_hook)(index, subindex, flags); + } + + return 0; +} + +/** Hook called from the slave stack ESC_stopoutputs to act on state changes + * forcing us to stop outputs. Here we can set them to a safe state. + */ +void APP_safeoutput(void) +{ + DPRINT("APP_safeoutput\n"); + + if (ESCvar.safeoutput_override != NULL) + { + (ESCvar.safeoutput_override)(); + } +} + +/** Write local process data to Sync Manager 3, Master Inputs. + */ +void TXPDO_update(void) +{ + if (ESCvar.txpdo_override != NULL) + { + (ESCvar.txpdo_override)(); + } + else + { + if (MAX_MAPPINGS_SM3 > 0) + { + COE_pdoPack(txpdo, ESCvar.sm3mappings, SMmap3); + } + ESC_write(ESC_SM3_sma, txpdo, ESCvar.ESC_SM3_sml); + } +} + +/** Read Sync Manager 2 to local process data, Master Outputs. + */ +void RXPDO_update(void) +{ + if (ESCvar.rxpdo_override != NULL) + { + (ESCvar.rxpdo_override)(); + } + else + { + ESC_read(ESC_SM2_sma, rxpdo, ESCvar.ESC_SM2_sml); + if (MAX_MAPPINGS_SM2 > 0) + { + COE_pdoUnpack(rxpdo, ESCvar.sm2mappings, SMmap2); + } + } +} + +/* Set the watchdog count value, don't have any affect when using + * HW watchdog 0x4xx + * + * @param[in] watchdogcnt = new watchdog count value + */ +void APP_setwatchdog(int watchdogcnt) +{ + CC_ATOMIC_SET(ESCvar.watchdogcnt, watchdogcnt); +} + +/* Function to update local I/O, call read ethercat outputs, call + * write ethercat inputs. Implement watch-dog counter to count-out if we have + * made state change affecting the App.state. + */ + +void DIG_process(uint16_t ALEvent, uint8_t flags) +{ + /* Handle watchdog */ + if ((flags & DIG_PROCESS_WD_FLAG) > 0) + { + if (CC_ATOMIC_GET(watchdog) > 0) + { + CC_ATOMIC_SUB(watchdog, 1); + } + + if ((CC_ATOMIC_GET(watchdog) <= 0) && + ((CC_ATOMIC_GET(ESCvar.App.state) & APPSTATE_OUTPUT) > 0)) + { + DPRINT("DIG_process watchdog expired\n"); + ESC_ALstatusgotoerror((ESCsafeop | ESCerror), ALERR_WATCHDOG); + } + else if (((CC_ATOMIC_GET(ESCvar.App.state) & APPSTATE_OUTPUT) == 0)) + { + CC_ATOMIC_SET(watchdog, ESCvar.watchdogcnt); + } + } + + /* Handle Outputs */ + if ((flags & DIG_PROCESS_OUTPUTS_FLAG) > 0) + { + if (((CC_ATOMIC_GET(ESCvar.App.state) & APPSTATE_OUTPUT) > 0) && + (ALEvent & ESCREG_ALEVENT_SM2)) + { + RXPDO_update(); + CC_ATOMIC_SET(watchdog, ESCvar.watchdogcnt); + /* Set outputs */ + cb_set_outputs(); + } + else if (ALEvent & ESCREG_ALEVENT_SM2) + { + RXPDO_update(); + } + } + + /* Call application */ + if ((flags & DIG_PROCESS_APP_HOOK_FLAG) > 0) + { + /* Call application callback if set */ + if (ESCvar.application_hook != NULL) + { + (ESCvar.application_hook)(); + } + } + + /* Handle Inputs */ + if ((flags & DIG_PROCESS_INPUTS_FLAG) > 0) + { + if (CC_ATOMIC_GET(ESCvar.App.state) > 0) + { + /* Update inputs */ + cb_get_inputs(); + TXPDO_update(); + } + } +} + +/* + * Handler for SM change, SM0/1, AL CONTROL and EEPROM events, the application + * control what interrupts that should be served and re-activated with + * event mask argument + */ +void ecat_slv_worker(uint32_t event_mask) +{ + do + { + /* Check the state machine */ + ESC_state(); + /* Check the SM activation event */ + ESC_sm_act_event(); + + /* Check mailboxes */ + while ((ESC_mbxprocess() > 0) || (ESCvar.txcue > 0)) + { + ESC_coeprocess(); +#if USE_FOE + ESC_foeprocess(); +#endif +#if USE_EOE + ESC_eoeprocess(); +#endif + ESC_xoeprocess(); + } +#if USE_EOE + ESC_eoeprocess_tx(); +#endif + /* Call emulated eeprom handler if set */ + if (ESCvar.esc_hw_eep_handler != NULL) + { + (ESCvar.esc_hw_eep_handler)(); + } + + CC_ATOMIC_SET(ESCvar.ALevent, ESC_ALeventread()); + + } while (ESCvar.ALevent & event_mask); + + ESC_ALeventmaskwrite(ESC_ALeventmaskread() | event_mask); +} + +/* + * Polling function. It should be called periodically for an application + * when only SM2/DC interrupt is active. + * Read and handle events for the EtherCAT state, status, mailbox and eeprom. + */ +void ecat_slv_poll(void) +{ + /* Read local time from ESC*/ + ESC_read(ESCREG_LOCALTIME, (void *)&ESCvar.Time, sizeof(ESCvar.Time)); + ESCvar.Time = etohl(ESCvar.Time); + + /* Check the state machine */ + ESC_state(); + /* Check the SM activation event */ + ESC_sm_act_event(); + + /* Check mailboxes */ + if (ESC_mbxprocess()) + { + ESC_coeprocess(); +#if USE_FOE + ESC_foeprocess(); +#endif +#if USE_EOE + ESC_eoeprocess(); +#endif + ESC_xoeprocess(); + } +#if USE_EOE + ESC_eoeprocess_tx(); +#endif + + /* Call emulated eeprom handler if set */ + if (ESCvar.esc_hw_eep_handler != NULL) + { + (ESCvar.esc_hw_eep_handler)(); + } +} + +/* + * Poll all events in a free-run application + */ +void ecat_slv(void) +{ + ecat_slv_poll(); + DIG_process(ESC_ALeventread(), DIG_PROCESS_WD_FLAG | DIG_PROCESS_OUTPUTS_FLAG | + DIG_PROCESS_APP_HOOK_FLAG | DIG_PROCESS_INPUTS_FLAG); +} + +/* + * Initialize the slave stack. + */ +void ecat_slv_init(esc_cfg_t *config) +{ + /* Init watchdog */ + watchdog = config->watchdog_cnt; + + /* Call stack configuration */ + ESC_config(config); + /* Call HW init */ + ESC_init(config); + + /* wait until ESC is started up */ + while ((ESCvar.DLstatus & 0x0001) == 0) + { + ESC_read(ESCREG_DLSTATUS, (void *)&ESCvar.DLstatus, + sizeof(ESCvar.DLstatus)); + ESCvar.DLstatus = etohs(ESCvar.DLstatus); + } + +#if USE_FOE + /* Init FoE */ + FOE_init(); +#endif + +#if USE_EOE + /* Init EoE */ + EOE_init(); +#endif + + /* reset ESC to init state */ + ESC_ALstatus(ESCinit); + ESC_ALerror(ALERR_NONE); + ESC_stopmbx(); + ESC_stopinput(); + ESC_stopoutput(); + /* Init Object Dictionary default values */ + COE_initDefaultValues(); +} diff --git a/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/ecat_slv.h b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/ecat_slv.h new file mode 100755 index 0000000..5f6b516 --- /dev/null +++ b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/ecat_slv.h @@ -0,0 +1,69 @@ +/* + * Licensed under the GNU General Public License version 2 with exceptions. See + * LICENSE file in the project root for full license information + */ + +#ifndef __ECAT_SLV_H__ +#define __ECAT_SLV_H__ + +#include "ecat_options.h" +#include "esc.h" + +/** + * This function is called when to get input values + */ +void cb_get_inputs(); + +/** + * This function is called when to set outputs values + */ +void cb_set_outputs(); + +/** Set the watchdog count value + * + * @param[in] watchdogcnt = new watchdog count value + */ +void APP_setwatchdog(int watchdogcnt); + +#define DIG_PROCESS_INPUTS_FLAG 0x01 +#define DIG_PROCESS_OUTPUTS_FLAG 0x02 +#define DIG_PROCESS_WD_FLAG 0x04 +#define DIG_PROCESS_APP_HOOK_FLAG 0x08 +/** Implements the watch-dog counter to count if we should make a state change + * due to missing incoming SM2 events. Updates local I/O and run the application + * in the following order, call read EtherCAT outputs, execute user provided + * application hook and call write EtherCAT inputs. + * + * @param[in] flags = User input what to execute + */ +void DIG_process(uint16_t ALEvent, uint8_t flags); + +/** + * Handler for SM change, SM0/1, AL CONTROL and EEPROM events, the application + * control what interrupts that should be served and re-activated with + * event mask argument + * + * @param[in] event_mask = Event mask for interrupts to serve and re-activate + * after served + */ +void ecat_slv_worker(uint32_t event_mask); + +/** + * Poll SM0/1, EEPROM and AL CONTROL events in a SM/DC synchronization + * application + */ +void ecat_slv_poll(void); + +/** + * Poll all events in a free-run application + */ +void ecat_slv(void); + +/** + * Initialize the slave stack + * + * @param[in] config = User input how to configure the stack + */ +void ecat_slv_init(esc_cfg_t *config); + +#endif /* __ECAT_SLV_H__ */ diff --git a/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/esc.c b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/esc.c new file mode 100755 index 0000000..582c1e0 --- /dev/null +++ b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/esc.c @@ -0,0 +1,1179 @@ +/* + * Licensed under the GNU General Public License version 2 with exceptions. See + * LICENSE file in the project root for full license information + */ +#include +#include +#include "esc.h" +#include "esc_coe.h" +#include "esc_foe.h" + +/** \file + * \brief + * Base EtherCAT functions for handling the Data Link Layer and Malilboxes + * + * + * State machine and mailbox support. + */ + +/** Write AL Status Code to the ESC. + * + * @param[in] errornumber = Write an by EtherCAT specified Error number register 0x134 AL Status Code + */ +void ESC_ALerror (uint16_t errornumber) +{ + uint16_t dummy; + ESCvar.ALerror = errornumber; + dummy = htoes (errornumber); + ESC_write (ESCREG_ALERROR, &dummy, sizeof (dummy)); +} + +/** Write AL Status to the ESC. + * + * @param[in] status = Write current slave status to register 0x130 AL Status + * reflecting actual state and error indication if present + */ +void ESC_ALstatus (uint8_t status) +{ + uint16_t dummy; + ESCvar.ALstatus = status; + dummy = htoes ((uint16_t) status); + ESC_write (ESCREG_ALSTATUS, &dummy, sizeof (dummy)); +} + +/** Write AL Status and AL Status code to the ESC. + * Call pre- and poststate change hook + * + * @param[in] status = Write current slave status to register 0x130 AL Status + * reflecting actual state and error indication if present + * @param[in] errornumber = Write an by EtherCAT specified Error number + * register 0x134 AL Status Code + */ +void ESC_ALstatusgotoerror (uint8_t status, uint16_t errornumber) +{ + uint8_t an, as; + + if(status & ESCop) + { + /* Erroneous input, ignore */ + return; + } + /* Mask error ack of current state */ + as = ESCvar.ALstatus & ESCREG_AL_ERRACKMASK; + an = as; + /* Set the state transition, new state in high bits and old in bits */ + as = ((status & ESCREG_AL_ERRACKMASK) << 4) | (as & 0x0f); + /* Call post state change hook case it have been configured */ + if (ESCvar.pre_state_change_hook != NULL) + { + ESCvar.pre_state_change_hook (&as, &an); + } + /* Stop outputs if active */ + if ((CC_ATOMIC_GET(ESCvar.App.state) & APPSTATE_OUTPUT) > 0) + { + ESC_stopoutput(); + } + ESC_ALerror(errornumber); + ESC_ALstatus(status); + an = status; + /* Call post state change hook case it have been configured */ + if (ESCvar.post_state_change_hook != NULL) + { + ESCvar.post_state_change_hook (&as, &an); + } +} + +/** Write ALeventMask register 0x204. + * + * @param[in] n = AL Event Mask + */ +void ESC_ALeventmaskwrite (uint32_t mask) +{ + uint32_t aleventmask; + aleventmask = htoel(mask); + ESC_write (ESCREG_ALEVENTMASK, &aleventmask, sizeof(aleventmask)); +} + +/** Read AleventMask register 0x204. + * + * @return value of register AL Event Mask + */ +uint32_t ESC_ALeventmaskread (void) +{ + uint32_t aleventmask; + + ESC_read (ESCREG_ALEVENTMASK, &aleventmask, sizeof(aleventmask)); + return htoel(aleventmask); +} + +/** Write ALevent register 0x220. + * + * @param[in] n = AL Event Mask + */ +void ESC_ALeventwrite (uint32_t event) +{ + uint32_t alevent; + alevent = htoel(event); + ESC_write (ESCREG_ALEVENT, &alevent, sizeof(alevent)); +} + +/** Read Alevent register 0x220. + * + * @return value of register AL Event Mask + */ +uint32_t ESC_ALeventread (void) +{ + uint32_t alevent; + ESC_read (ESCREG_ALEVENT, &alevent, sizeof(alevent)); + return htoel(alevent); +} + +/** Read SM Activate register 0x806(+ offset to SyncManager n) to acknowledge a + * Sync Manager event Bit 3 in ALevent. The result is not used. + * + * @param[in] n = Read Sync Manager no. n + */ +void ESC_SMack (uint8_t n) +{ + uint8_t dummy; + ESC_read (ESCREG_SM0ACTIVATE + (n << 3), &dummy, 1); +} + +/** Read SM Status register 0x805(+ offset to SyncManager n) and save the + * result in global variable ESCvar.SM[n]. + * + * @param[in] n = Read Sync Manager no. n + */ +void ESC_SMstatus (uint8_t n) +{ + _ESCsm2 *sm; + sm = (_ESCsm2 *)&ESCvar.SM[n]; + ESC_read (ESCREG_SM0STATUS + (n << 3), &(sm->Status), 1); +} + +/** Write ESCvar.SM[n] data to ESC PDI control register 0x807(+ offset to SyncManager n). + * + * @param[in] n = Write to Sync Manager no. n + */ +void ESC_SMwritepdi (uint8_t n) +{ + _ESCsm2 *sm; + sm = (_ESCsm2 *)&ESCvar.SM[n]; + ESC_write (ESCREG_SM0PDI + (n << 3), &(sm->ActPDI), 1); +} + +/** Write 0 to Bit0 in SM PDI control register 0x807(+ offset to SyncManager n) to Activate the Sync Manager n. + * + * @param[in] n = Write to Sync Manager no. n + */ +void ESC_SMenable (uint8_t n) +{ + _ESCsm2 *sm; + sm = (_ESCsm2 *)&ESCvar.SM[n]; + sm->ActPDI &= ~ESCREG_SMENABLE_BIT; + ESC_SMwritepdi (n); +} +/** Write 1 to Bit0 in SM PDI control register 0x807(+ offset to SyncManager n) to De-activte the Sync Manager n. + * + * @param[in] n = Write to Sync Manager no. n + */ +void ESC_SMdisable (uint8_t n) +{ + _ESCsm2 *sm; + sm = (_ESCsm2 *)&ESCvar.SM[n]; + sm->ActPDI |= ESCREG_SMENABLE_BIT; + ESC_SMwritepdi (n); +} +/** Read Configured Station Address register 0x010 assigned by the Master. + * + */ +void ESC_address (void) +{ + ESC_read (ESCREG_ADDRESS, (void *) &ESCvar.address, sizeof (ESCvar.address)); + ESCvar.address = etohs (ESCvar.address); +} + +/** Read Watchdog Status register 0x440. Result Bit0 0= Expired, 1= Active or disabled. + * + * @return value of register Watchdog Status. + */ +uint8_t ESC_WDstatus (void) +{ + uint16_t wdstatus; + ESC_read (ESCREG_WDSTATUS, &wdstatus, 2); + wdstatus = etohs (wdstatus); + return (uint8_t) wdstatus; +} + +/** Read SYNC Out Unit activation registers 0x981 + * + * @return value of register Activation. + */ +uint8_t ESC_SYNCactivation (void) +{ + uint8_t activation; + ESC_read (ESCREG_SYNC_ACT, &activation, sizeof(activation)); + return activation; +} + +/** Read SYNC0 cycle time + * + * @return value of register SYNC0 cycle time + */ +uint32_t ESC_SYNC0cycletime (void) +{ + uint32_t cycletime; + ESC_read (ESCREG_SYNC0_CYCLE_TIME, &cycletime, sizeof(cycletime)); + cycletime = etohl (cycletime); + return cycletime; +} + +/** Read SYNC1 cycle time + * + * @return value of register SYNC1 cycle time + */ +uint32_t ESC_SYNC1cycletime (void) +{ + uint32_t cycletime; + ESC_read (ESCREG_SYNC1_CYCLE_TIME, &cycletime, 4); + cycletime = etohl (cycletime); + return cycletime; +} + + +/** Validate the DC values if the SYNC unit is activated. + * + * @return = 0 if OK, else ERROR code to be set by caller. + */ +uint16_t ESC_checkDC (void) +{ + uint16_t ret = 0; + + uint8_t sync_act = ESC_SYNCactivation(); + /* Do we need to check sync settings? */ + if((sync_act & (ESCREG_SYNC_ACT_ACTIVATED | ESCREG_SYNC_AUTO_ACTIVATED)) > 0) + { + /* Trigger a by the application given DC check handler, return error if + * non is given + */ + ret = ALERR_DCINVALIDSYNCCFG; + if(ESCvar.esc_check_dc_handler != NULL) + { + ret = (ESCvar.esc_check_dc_handler)(); + } + } + else + { + ESCvar.dcsync = 0; + ESCvar.synccounter = 0; + } + + return ret; +} + +/** Check mailbox status by reading all SyncManager 0 and 1 data. The read values + * are compared with local definitions for SM Physical Address, SM Length and SM Control. + * If we check fails we disable Mailboxes by disabling SyncManager 0 and 1 and return + * state Init with Error flag set. + * + * @param[in] state = Current state request read from ALControl 0x0120 + * @return if all Mailbox values is correct we return incoming state request, otherwise + * we return state Init with Error flag set. + */ +uint8_t ESC_checkmbx (uint8_t state) +{ + _ESCsm2 *SM; + ESC_read (ESCREG_SM0, (void *) &ESCvar.SM[0], sizeof (ESCvar.SM[0])); + ESC_read (ESCREG_SM1, (void *) &ESCvar.SM[1], sizeof (ESCvar.SM[1])); + SM = (_ESCsm2 *) & ESCvar.SM[0]; + if ((etohs (SM->PSA) != ESC_MBX0_sma) || (etohs (SM->Length) != ESC_MBX0_sml) + || (SM->Command != ESC_MBX0_smc) || (ESCvar.SM[0].ECsm == 0)) + { + ESCvar.SMtestresult = SMRESULT_ERRSM0; + ESC_SMdisable (0); + ESC_SMdisable (1); + return (uint8_t) (ESCinit | ESCerror); //fail state change + } + SM = (_ESCsm2 *) & ESCvar.SM[1]; + if ((etohs (SM->PSA) != ESC_MBX1_sma) || (etohs (SM->Length) != ESC_MBX1_sml) + || (SM->Command != ESC_MBX1_smc) || (ESCvar.SM[1].ECsm == 0)) + { + ESCvar.SMtestresult = SMRESULT_ERRSM1; + ESC_SMdisable (0); + ESC_SMdisable (1); + return ESCinit | ESCerror; //fail state change + } + return state; +} +/** Try to start mailboxes for current ALControl state request by enabling SyncManager 0 and 1. + * If all mailbox settings is correct we return incoming state request, otherwise + * we return state Init with Error flag set and update local ALerror with code 0x16 Invalid + * mailbox configuration. + * + * @param[in] state = Current state request read from ALControl 0x0120 + * @return if all Mailbox values is correct we return incoming state, otherwise + * we return state Init with Error flag set. + */ +uint8_t ESC_startmbx (uint8_t state) +{ + /* Assign SM settings */ + ESCvar.activembxsize = MBXSIZE; + ESCvar.activemb0 = &ESCvar.mb[0]; + ESCvar.activemb1 = &ESCvar.mb[1]; + + + ESC_SMenable (0); + ESC_SMenable (1); + ESC_SMstatus (0); + ESC_SMstatus (1); + if ((state = ESC_checkmbx (state)) & ESCerror) + { + ESC_ALerror (ALERR_INVALIDMBXCONFIG); + ESCvar.MBXrun = 0; + } + else + { + ESCvar.toggle = ESCvar.SM[1].ECrep; //sync repeat request toggle state + ESCvar.MBXrun = 1; + } + return state; +} + +/** Try to start bootstrap mailboxes for current ALControl state request by enabling SyncManager 0 and 1. + * If all mailbox settings is correct we return incoming state request, otherwise + * we return state Init with Error flag set and update local ALerror with code 0x16 Invalid + * mailbox configuration. + * + * @param[in] state = Current state request read from ALControl 0x0120 + * @return if all Mailbox values is correct we return incoming state, otherwise + * we return state Init with Error flag set. + */ +uint8_t ESC_startmbxboot (uint8_t state) +{ + /* Assign SM settings */ + ESCvar.activembxsize = MBXSIZEBOOT; + ESCvar.activemb0 = &ESCvar.mbboot[0]; + ESCvar.activemb1 = &ESCvar.mbboot[1]; + + ESC_SMenable (0); + ESC_SMenable (1); + ESC_SMstatus (0); + ESC_SMstatus (1); + if ((state = ESC_checkmbx (state)) & ESCerror) + { + ESC_ALerror (ALERR_INVALIDBOOTMBXCONFIG); + ESCvar.MBXrun = 0; + } + else + { + ESCvar.toggle = ESCvar.SM[1].ECrep; //sync repeat request toggle state + ESCvar.MBXrun = 1; + } + return state; +} + +/** Stop mailboxes by disabling SyncManager 0 and 1. Clear local mailbox variables + * stored in ESCvar. + */ +void ESC_stopmbx (void) +{ + uint8_t n; + ESCvar.MBXrun = 0; + ESC_SMdisable (0); + ESC_SMdisable (1); + for (n = 0; n < ESC_MBXBUFFERS; n++) + { + MBXcontrol[n].state = MBXstate_idle; + } + ESCvar.mbxoutpost = 0; + ESCvar.mbxbackup = 0; + ESCvar.xoe = 0; + ESCvar.mbxfree = 1; + ESCvar.toggle = 0; + ESCvar.mbxincnt = 0; + ESCvar.segmented = 0; + ESCvar.frags = 0; + ESCvar.fragsleft = 0; + ESCvar.txcue = 0; + ESCvar.index = 0; + ESCvar.subindex = 0; + ESCvar.flags = 0; +} + +/** Read Receive mailbox and store data in local ESCvar.MBX variable. + * Combined function for bootstrap and other states. State check decides + * which one to read. + */ +void ESC_readmbx (void) +{ + _MBX *MB = (_MBX *)&MBX[0]; + uint16_t length; + + ESC_read (ESC_MBX0_sma, MB, ESC_MBXHSIZE); + length = etohs (MB->header.length); + + if (length > (ESC_MBX0_sml - ESC_MBXHSIZE)) + { + length = ESC_MBX0_sml - ESC_MBXHSIZE; + } + ESC_read (ESC_MBX0_sma + ESC_MBXHSIZE, MB->b, length); + if (length + ESC_MBXHSIZE < ESC_MBX0_sml) + { + ESC_read (ESC_MBX0_sme, &length, 1); + } + + MBXcontrol[0].state = MBXstate_inclaim; +} +/** Write local mailbox buffer ESCvar.MBX[n] to Send mailbox. + * Combined function for bootstrap and other states. State check decides + * which one to write. + * + * @param[in] n = Which local mailbox buffer n to send. + */ +void ESC_writembx (uint8_t n) +{ + _MBXh *MBh = (_MBXh *)&MBX[n * ESC_MBXSIZE]; + uint8_t dummy = 0; + uint16_t length; + length = etohs (MBh->length); + + if (length > (ESC_MBX1_sml - ESC_MBXHSIZE)) + { + length = ESC_MBX1_sml - ESC_MBXHSIZE; + } + ESC_write (ESC_MBX1_sma, MBh, ESC_MBXHSIZE + length); + if (length + ESC_MBXHSIZE < ESC_MBX1_sml) + { + ESC_write (ESC_MBX1_sme, &dummy, 1); + } + + ESCvar.mbxfree = 0; +} + +/** TBD + */ +void ESC_ackmbxread (void) +{ + uint8_t dummy = 0; + + ESC_write (ESC_MBX1_sma, &dummy, 1); + ESCvar.mbxfree = 1; +} + +/** Allocate and prepare a mailbox buffer. Take the first Idle buffer from the End. + * Set Mailbox control state to be used for outbox and fill the mailbox buffer with + * address master and mailbox next CNT value between 1-7. + * + * @return The index of Mailbox buffer prepared for outbox. IF no buffer is available return 0. + */ +uint8_t ESC_claimbuffer (void) +{ + _MBXh *MBh; + uint8_t n = ESC_MBXBUFFERS - 1; + while ((n > 0) && (MBXcontrol[n].state)) + { + n--; + } + if (n) + { + MBXcontrol[n].state = MBXstate_outclaim; + MBh = (_MBXh *)&MBX[n * ESC_MBXSIZE]; + ESCvar.mbxcnt++; + ESCvar.mbxcnt = (ESCvar.mbxcnt & 0x07); + if (ESCvar.mbxcnt == 0) + { + ESCvar.mbxcnt = 1; + } + MBh->address = htoes (0x0000); // destination is master + MBh->channel = 0; + MBh->priority = 0; + MBh->mbxcnt = ESCvar.mbxcnt; + ESCvar.txcue++; + } + return n; +} + +/** Look for any present requests for posting to the outbox. + * + * @return the index of Mailbox buffer ready to be posted. + */ +uint8_t ESC_outreqbuffer (void) +{ + uint8_t n = ESC_MBXBUFFERS - 1; + while ((n > 0) && (MBXcontrol[n].state != MBXstate_outreq)) + { + n--; + } + return n; +} +/** Allocate and prepare a mailbox buffer for sending an error message. Take the first Idle + * buffer from the end. Set Mailbox control state to be used for outbox and fill the mailbox + * buffer with error information. + * + * @param[in] n = Error number to be sent in mailbox error message. + */ +void MBX_error (uint16_t error) +{ + uint8_t MBXout; + _MBXerr *mbxerr; + MBXout = ESC_claimbuffer (); + if (MBXout) + { + mbxerr = (_MBXerr *) &MBX[MBXout * ESC_MBXSIZE]; + mbxerr->mbxheader.length = htoes ((uint16_t) 0x04); + mbxerr->mbxheader.mbxtype = MBXERR; + mbxerr->type = htoes ((uint16_t) 0x01); + mbxerr->detail = htoes (error); + MBXcontrol[MBXout].state = MBXstate_outreq; + } +} + +/** Mailbox routine for implementing the low-level part of the mailbox protocol + * used by Application Layers running on-top of mailboxes. It takes care of sending + * a mailbox, re-sending a mailbox, reading a mailbox and handles a mailbox full event. + * + * @return =0 if nothing to do. =1 if something to be handled by mailbox protocols. + */ +uint8_t ESC_mbxprocess (void) +{ + uint8_t mbxhandle = 0; + _MBXh *MBh = (_MBXh *)&MBX[0]; + + if (ESCvar.MBXrun == 0) + { + /* nothing to do */ + return 0; + } + + /* SM0/1 access */ + if (ESCvar.ALevent & (ESCREG_ALEVENT_SM0 | ESCREG_ALEVENT_SM1)) + { + ESC_SMstatus (0); + ESC_SMstatus (1); + } + + /* outmbx read by master */ + if (ESCvar.mbxoutpost && (ESCvar.ALevent & ESCREG_ALEVENT_SM1)) + { + ESC_ackmbxread (); + /* dispose old backup */ + if (ESCvar.mbxbackup) + { + MBXcontrol[ESCvar.mbxbackup].state = MBXstate_idle; + } + /* if still to do */ + if (MBXcontrol[ESCvar.mbxoutpost].state == MBXstate_again) + { + ESC_writembx (ESCvar.mbxoutpost); + } + /* create new backup */ + MBXcontrol[ESCvar.mbxoutpost].state = MBXstate_backup; + ESCvar.mbxbackup = ESCvar.mbxoutpost; + ESCvar.mbxoutpost = 0; + /* Do we have any ongoing protocol transfers, return 1 */ + if(ESCvar.xoe > 0) + { + return 1; + } + return 0; + } + + /* repeat request */ + if (ESCvar.SM[1].ECrep != ESCvar.toggle) + { + if (ESCvar.mbxoutpost || ESCvar.mbxbackup) + { + /* if outmbx empty */ + if (ESCvar.mbxoutpost == 0) + { + /* use backup mbx */ + ESC_writembx (ESCvar.mbxbackup); + } + else + { + /* reset mailbox */ + ESC_SMdisable (1); + /* have to resend later */ + MBXcontrol[ESCvar.mbxoutpost].state = MBXstate_again; + /* activate mailbox */ + ESC_SMenable (1); + /* use backup mbx */ + ESC_writembx (ESCvar.mbxbackup); + } + ESCvar.toggle = ESCvar.SM[1].ECrep; + ESCvar.SM[1].PDIrep = ESCvar.toggle; + ESC_SMwritepdi (1); + } + return 0; + } + + /* if the outmailbox is free check if we have something to send */ + if (ESCvar.txcue && (ESCvar.mbxfree || !ESCvar.SM[1].MBXstat)) + { + /* check out request mbx */ + mbxhandle = ESC_outreqbuffer (); + /* outmbx empty and outreq mbx available */ + if (mbxhandle) + { + ESC_writembx (mbxhandle); + /* Refresh SM status */ + ESC_SMstatus (1); + /* change state */ + MBXcontrol[mbxhandle].state = MBXstate_outpost; + ESCvar.mbxoutpost = mbxhandle; + if (ESCvar.txcue) + { + ESCvar.txcue--; + } + } + } + + /* read mailbox if full and no xoe in progress */ + if ((ESCvar.SM[0].MBXstat != 0) && (MBXcontrol[0].state == 0) + && (ESCvar.mbxoutpost == 0) && (ESCvar.xoe == 0)) + { + ESC_readmbx (); + ESCvar.SM[0].MBXstat = 0; + if (etohs (MBh->length) == 0) + { + MBX_error (MBXERR_INVALIDHEADER); + /* drop mailbox */ + MBXcontrol[0].state = MBXstate_idle; + } + if ((MBh->mbxcnt != 0) && (MBh->mbxcnt == ESCvar.mbxincnt)) + { + /* drop mailbox */ + MBXcontrol[0].state = MBXstate_idle; + } + ESCvar.mbxincnt = MBh->mbxcnt; + return 1; + } + + return 0; +} +/** Handler for incorrect or unsupported mailbox data. Write error response + * in Mailbox. + */ +void ESC_xoeprocess (void) +{ + _MBXh *mbh; + if (ESCvar.MBXrun == 0) + { + return; + } + if ((ESCvar.xoe == 0) && (MBXcontrol[0].state == MBXstate_inclaim)) + { + mbh = (_MBXh *) &MBX[0]; + if ((mbh->mbxtype == 0) || (etohs (mbh->length) == 0)) + { + MBX_error (MBXERR_INVALIDHEADER); + } + else + { + MBX_error (MBXERR_UNSUPPORTEDPROTOCOL); + } + /* mailbox type not supported, drop mailbox */ + MBXcontrol[0].state = MBXstate_idle; + } +} + +/** Validate the values of Sync Manager 2 & 3 that the current ESC values is + * equal to configured and calculated local values. + * + * @param[in] state = Requested state. + * @return = incoming state request if every thing checks out OK. = state (PREOP | ERROR) if something isn't correct. + */ +uint8_t ESC_checkSM23 (uint8_t state) +{ + _ESCsm2 *SM; + ESC_read (ESCREG_SM2, (void *) &ESCvar.SM[2], sizeof (ESCvar.SM[2])); + SM = (_ESCsm2 *) & ESCvar.SM[2]; + if ((etohs (SM->PSA) != ESC_SM2_sma) || (etohs (SM->Length) != ESCvar.ESC_SM2_sml) + || (SM->Command != ESC_SM2_smc) || !(SM->ActESC & ESC_SM2_act)) + { + ESCvar.SMtestresult = SMRESULT_ERRSM2; + /* fail state change */ + return (ESCpreop | ESCerror); + } + if ((ESC_SM2_sma + (etohs (SM->Length) * 3)) > ESC_SM3_sma) + { + ESCvar.SMtestresult = SMRESULT_ERRSM2; + /* SM2 overlaps SM3, fail state change */ + return (ESCpreop | ESCerror); + } + ESC_read (ESCREG_SM3, (void *) &ESCvar.SM[3], sizeof (ESCvar.SM[3])); + SM = (_ESCsm2 *) & ESCvar.SM[3]; + if ((etohs (SM->PSA) != ESC_SM3_sma) || (etohs (SM->Length) != ESCvar.ESC_SM3_sml) + || (SM->Command != ESC_SM3_smc) || !(SM->ActESC & ESC_SM3_act)) + { + ESCvar.SMtestresult = SMRESULT_ERRSM3; + /* fail state change */ + return (ESCpreop | ESCerror); + } + return state; +} + +/** Function trying to enable start updating the process data inputs. It calls the check SM 2 & 3 + * routine, based on the result from there if enables or disables the Input SyncManager, in addition + * it updates the ALStatusCode case something didn't pass the check. + * + * @param[in] state = Requested state. + * @return = state, incoming state request if every thing checks out OK. =state (PREOP | ERROR) if something isn't correct. + */ +uint8_t ESC_startinput (uint8_t state) +{ + + state = ESC_checkSM23 (state); + + if (state != (ESCpreop | ESCerror)) + { + ESC_SMenable (3); + CC_ATOMIC_SET(ESCvar.App.state, APPSTATE_INPUT); + } + else + { + ESC_SMdisable (2); + ESC_SMdisable (3); + if (ESCvar.SMtestresult & SMRESULT_ERRSM3) + { + ESC_ALerror (ALERR_INVALIDINPUTSM); + } + else + { + ESC_ALerror (ALERR_INVALIDOUTPUTSM); + } + } + + /* Exit here if polling */ + if (ESCvar.use_interrupt == 0) + { + return state; + } + + if (state != (ESCpreop | ESCerror)) + { + uint16_t dc_check_result; + dc_check_result = ESC_checkDC(); + if(dc_check_result > 0) + { + ESC_ALerror (dc_check_result); + state = (ESCpreop | ESCerror); + + ESC_SMdisable (2); + ESC_SMdisable (3); + CC_ATOMIC_SET(ESCvar.App.state, APPSTATE_IDLE); + } + else + { + if (ESCvar.esc_hw_interrupt_enable != NULL) + { + if(ESCvar.dcsync > 0) + { + ESCvar.esc_hw_interrupt_enable(ESCREG_ALEVENT_DC_SYNC0 | + ESCREG_ALEVENT_SM2); + } + else + { + ESCvar.esc_hw_interrupt_enable(ESCREG_ALEVENT_SM2); + } + } + } + } + + return state; +} + +/** Unconditional stop of updating inputs by disabling Sync Manager 2 & 3. + * Set the App.state to APPSTATE_IDLE. + * + */ +void ESC_stopinput (void) +{ + CC_ATOMIC_SET(ESCvar.App.state, APPSTATE_IDLE); + ESC_SMdisable (3); + ESC_SMdisable (2); + + /* Call interrupt disable hook case it have been configured */ + if ((ESCvar.use_interrupt != 0) && + (ESCvar.esc_hw_interrupt_disable != NULL)) + { + ESCvar.esc_hw_interrupt_disable (ESCREG_ALEVENT_DC_SYNC0 | + ESCREG_ALEVENT_SM2); + } +} + + +/** Unconditional start of updating outputs by enabling Sync Manager 2. + * Set the App.state to APPSTATE_OUTPUT. + * + * @param[in] state = Not used. + * @return = state unchanged. + * + */ +uint8_t ESC_startoutput (uint8_t state) +{ + + ESC_SMenable (2); + CC_ATOMIC_OR(ESCvar.App.state, APPSTATE_OUTPUT); + return state; + +} + +/** Unconditional stop of updating outputs by disabling Sync Manager 2. + * Set the App.state to APPSTATE_INPUT. Call application hook APP_safeoutput + * letting the user to set safe state values on outputs. + * + */ +void ESC_stopoutput (void) +{ + CC_ATOMIC_AND(ESCvar.App.state, APPSTATE_INPUT); + ESC_SMdisable (2); + APP_safeoutput (); +} + +/** The state handler acting on SyncManager Activation BIT(4) + * events in the Al Event Request register 0x220. + * + */ +void ESC_sm_act_event (void) +{ + uint8_t ac, an, as, ax, ax23; + + /* Have at least on Sync Manager changed */ + if ((ESCvar.ALevent & ESCREG_ALEVENT_SMCHANGE) == 0) + { + /* nothing to do */ + return; + } + + /* Mask state request bits + Error ACK */ + ac = ESCvar.ALcontrol & ESCREG_AL_STATEMASK; + as = ESCvar.ALstatus & ESCREG_AL_STATEMASK; + an = as; + if (((ac & ESCerror) || (ac == ESCinit))) + { + /* if error bit confirmed reset */ + ac &= ESCREG_AL_ERRACKMASK; + an &= ESCREG_AL_ERRACKMASK; + } + /* Enter SM changed handling for all steps but Init and Boot when Mailboxes + * is up and running + */ + if ((as & ESCREG_AL_ALLBUTINITMASK) && + ((as == ESCboot) == 0) && ESCvar.MBXrun) + { + /* Validate Sync Managers, reading the Activation register will + * acknowledge the SyncManager Activation event making us enter + * this execution path. + */ + ax = ESC_checkmbx (as); + ax23 = ESC_checkSM23 (as); + if ((an & ESCerror) && ((ac & ESCerror) == 0)) + { + /* if in error then stay there */ + } + /* Have we been forced to step down to INIT we will stop mailboxes, + * update AL Status Code and exit ESC_state + */ + else if (ax == (ESCinit | ESCerror)) + { + /* If we have activated Inputs and Outputs we need to disable them */ + if (CC_ATOMIC_GET(ESCvar.App.state)) + { + ESC_stopoutput (); + ESC_stopinput (); + } + /* Stop mailboxes and update ALStatus code */ + ESC_stopmbx (); + ESC_ALerror (ALERR_INVALIDMBXCONFIG); + ESCvar.MBXrun = 0; + ESC_ALstatus (ax); + return; + } + /* Have we been forced to step down to PREOP we will stop inputs + * and outputs, update AL Status Code and exit ESC_state + */ + else if (CC_ATOMIC_GET(ESCvar.App.state) && (ax23 == (ESCpreop | ESCerror))) + { + ESC_stopoutput (); + ESC_stopinput (); + if (ESCvar.SMtestresult & SMRESULT_ERRSM3) + { + ESC_ALerror (ALERR_INVALIDINPUTSM); + } + else + { + ESC_ALerror (ALERR_INVALIDOUTPUTSM); + } + ESC_ALstatus (ax23); + } + } + else + { + ESC_SMack (0); + ESC_SMack (1); + ESC_SMack (2); + ESC_SMack (3); + ESC_SMack (4); + ESC_SMack (5); + ESC_SMack (6); + ESC_SMack (7); + } +} +/** The state handler acting on ALControl Bit(0) + * events in the Al Event Request register 0x220. + * + */ +void ESC_state (void) +{ + uint8_t ac, an, as; + + /* Do we have a state change request pending */ + if (ESCvar.ALevent & ESCREG_ALEVENT_CONTROL) + { + ESC_read (ESCREG_ALCONTROL, (void *) &ESCvar.ALcontrol, + sizeof (ESCvar.ALcontrol)); + ESCvar.ALcontrol = etohs (ESCvar.ALcontrol); + } + else + { + /* nothing to do */ + return; + } + /* Mask state request bits + Error ACK */ + ac = ESCvar.ALcontrol & ESCREG_AL_STATEMASK; + as = ESCvar.ALstatus & ESCREG_AL_STATEMASK; + an = as; + if (((ac & ESCerror) || (ac == ESCinit))) + { + /* if error bit confirmed reset */ + ac &= ESCREG_AL_ERRACKMASK; + an &= ESCREG_AL_ERRACKMASK; + } + + /* Error state not acked, leave original */ + if ((an & ESCerror) && ((ac & ESCerror) == 0)) + { + return; + } + + /* Mask high bits ALcommand, low bits ALstatus */ + as = (ac << 4) | (as & 0x0f); + + /* Call post state change hook case it have been configured */ + if (ESCvar.pre_state_change_hook != NULL) + { + ESCvar.pre_state_change_hook (&as, &an); + } + + /* Switch through the state change requested via AlControl from + * current state read in AL status + */ + switch (as) + { + case INIT_TO_INIT: + case PREOP_TO_PREOP: + case OP_TO_OP: + { + break; + } + case INIT_TO_PREOP: + { + /* get station address */ + ESC_address (); + an = ESC_startmbx (ac); + break; + } + case INIT_TO_BOOT: + case BOOT_TO_BOOT: + { + /* get station address */ + ESC_address (); + an = ESC_startmbxboot (ac); + break; + } + case INIT_TO_SAFEOP: + case INIT_TO_OP: + { + an = ESCinit | ESCerror; + ESC_ALerror (ALERR_INVALIDSTATECHANGE); + break; + } + case OP_TO_INIT: + { + ESC_stopoutput (); + ESC_stopinput (); + ESC_stopmbx (); + an = ESCinit; + break; + } + case SAFEOP_TO_INIT: + { + ESC_stopinput (); + ESC_stopmbx (); + an = ESCinit; + break; + } + case PREOP_TO_INIT: + { + ESC_stopmbx (); + an = ESCinit; + break; + } + case BOOT_TO_INIT: + { + ESC_stopmbx (); + an = ESCinit; + break; + } + case PREOP_TO_BOOT: + case BOOT_TO_PREOP: + case BOOT_TO_SAFEOP: + case BOOT_TO_OP: + { + an = ESCpreop | ESCerror; + ESC_ALerror (ALERR_INVALIDSTATECHANGE); + break; + } + case PREOP_TO_SAFEOP: + case SAFEOP_TO_SAFEOP: + { + ESCvar.ESC_SM2_sml = sizeOfPDO (RX_PDO_OBJIDX, &ESCvar.sm2mappings, + SMmap2, MAX_MAPPINGS_SM2); + if (ESCvar.sm2mappings < 0) + { + an = ESCpreop | ESCerror; + ESC_ALerror (ALERR_INVALIDOUTPUTSM); + break; + } + + ESCvar.ESC_SM3_sml = sizeOfPDO (TX_PDO_OBJIDX, &ESCvar.sm3mappings, + SMmap3, MAX_MAPPINGS_SM3); + if (ESCvar.sm3mappings < 0) + { + an = ESCpreop | ESCerror; + ESC_ALerror (ALERR_INVALIDINPUTSM); + break; + } + + an = ESC_startinput (ac); + if (an == ac) + { + ESC_SMenable (2); + } + break; + } + case PREOP_TO_OP: + { + an = ESCpreop | ESCerror; + ESC_ALerror (ALERR_INVALIDSTATECHANGE); + break; + } + case OP_TO_PREOP: + { + ESC_stopoutput (); + ESC_stopinput (); + an = ESCpreop; + break; + } + case SAFEOP_TO_PREOP: + { + ESC_stopinput (); + an = ESCpreop; + break; + } + case SAFEOP_TO_BOOT: + { + an = ESCsafeop | ESCerror; + ESC_ALerror (ALERR_INVALIDSTATECHANGE); + break; + } + case SAFEOP_TO_OP: + { + an = ESC_startoutput (ac); + break; + } + case OP_TO_BOOT: + { + an = ESCsafeop | ESCerror; + ESC_ALerror (ALERR_INVALIDSTATECHANGE); + ESC_stopoutput (); + break; + } + case OP_TO_SAFEOP: + { + an = ESCsafeop; + ESC_stopoutput (); + break; + } + default: + { + if (an == ESCop) + { + ESC_stopoutput (); + an = ESCsafeop; + } + if (as == ESCsafeop) + { + ESC_stopinput (); + } + an |= ESCerror; + ESC_ALerror (ALERR_UNKNOWNSTATE); + break; + } + } + + /* Call post state change hook case it have been configured */ + if (ESCvar.post_state_change_hook != NULL) + { + ESCvar.post_state_change_hook (&as, &an); + } + + if (!(an & ESCerror) && (ESCvar.ALerror)) + { + /* clear error */ + ESC_ALerror (ALERR_NONE); + } + + ESC_ALstatus (an); + DPRINT ("state %x\n", an); +} +/** Function copying the application configuration variable + * data to the stack local variable. + * + * @param[in] cfg = Pointer to the Application configuration variable + * holding application specific details. Data is copied. + */ +void ESC_config (esc_cfg_t * cfg) +{ + static sm_cfg_t mb0 = {MBX0_sma, MBX0_sml, MBX0_sme, MBX0_smc, 0}; + static sm_cfg_t mb1 = {MBX1_sma, MBX1_sml, MBX1_sme, MBX1_smc, 0}; + static sm_cfg_t mbboot0 = {MBX0_sma_b, MBX0_sml_b, MBX0_sme_b, MBX0_smc_b, 0}; + static sm_cfg_t mbboot1 = {MBX1_sma_b, MBX1_sml_b, MBX1_sme_b, MBX1_smc_b, 0}; + + /* Configure stack */ + ESCvar.use_interrupt = cfg->use_interrupt; + ESCvar.watchdogcnt = cfg->watchdog_cnt; + + ESCvar.mb[0] = mb0; + ESCvar.mb[1] = mb1; + ESCvar.mbboot[0] = mbboot0; + ESCvar.mbboot[1] = mbboot1; + + ESCvar.skip_default_initialization = cfg->skip_default_initialization; + ESCvar.set_defaults_hook = cfg->set_defaults_hook; + ESCvar.pre_state_change_hook = cfg->pre_state_change_hook; + ESCvar.post_state_change_hook = cfg->post_state_change_hook; + ESCvar.application_hook = cfg->application_hook; + ESCvar.safeoutput_override = cfg->safeoutput_override; + ESCvar.pre_object_download_hook = cfg->pre_object_download_hook; + ESCvar.post_object_download_hook = cfg->post_object_download_hook; + ESCvar.pre_object_upload_hook = cfg->pre_object_upload_hook; + ESCvar.post_object_upload_hook = cfg->post_object_upload_hook; + ESCvar.rxpdo_override = cfg->rxpdo_override; + ESCvar.txpdo_override = cfg->txpdo_override; + ESCvar.esc_hw_interrupt_enable = cfg->esc_hw_interrupt_enable; + ESCvar.esc_hw_interrupt_disable = cfg->esc_hw_interrupt_disable; + ESCvar.esc_hw_eep_handler = cfg->esc_hw_eep_handler; + ESCvar.esc_check_dc_handler = cfg->esc_check_dc_handler; +} diff --git a/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/esc.h b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/esc.h new file mode 100755 index 0000000..2d08e61 --- /dev/null +++ b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/esc.h @@ -0,0 +1,769 @@ +/* + * Licensed under the GNU General Public License version 2 with exceptions. See + * LICENSE file in the project root for full license information + */ + +/** \file + * \brief + * Headerfile for esc.h + */ + +#ifndef __esc__ +#define __esc__ + +#include +#include +#include +#include "ecat_options.h" + +#define ESCREG_ADDRESS 0x0010 +#define ESCREG_DLSTATUS 0x0110 +#define ESCREG_ALCONTROL 0x0120 +#define ESCREG_ALSTATUS 0x0130 +#define ESCREG_ALERROR 0x0134 +#define ESCREG_ALEVENTMASK 0x0204 +#define ESCREG_ALEVENT 0x0220 +#define ESCREG_ALEVENT_SM_MASK 0x0310 +#define ESCREG_ALEVENT_SMCHANGE 0x0010 +#define ESCREG_ALEVENT_CONTROL 0x0001 +#define ESCREG_ALEVENT_DC_LATCH 0x0002 +#define ESCREG_ALEVENT_DC_SYNC0 0x0004 +#define ESCREG_ALEVENT_DC_SYNC1 0x0008 +#define ESCREG_ALEVENT_EEP 0x0020 +#define ESCREG_ALEVENT_WD 0x0040 +#define ESCREG_ALEVENT_SM0 0x0100 +#define ESCREG_ALEVENT_SM1 0x0200 +#define ESCREG_ALEVENT_SM2 0x0400 +#define ESCREG_ALEVENT_SM3 0x0800 +#define ESCREG_WDSTATUS 0x0440 +#define ESCREG_EECONTSTAT 0x0502 +#define ESCREG_EEDATA 0x0508 +#define ESCREG_SM0 0x0800 +#define ESCREG_SM0STATUS (ESCREG_SM0 + 5) +#define ESCREG_SM0ACTIVATE (ESCREG_SM0 + 6) +#define ESCREG_SM0PDI (ESCREG_SM0 + 7) +#define ESCREG_SM1 (ESCREG_SM0 + 0x08) +#define ESCREG_SM2 (ESCREG_SM0 + 0x10) +#define ESCREG_SM3 (ESCREG_SM0 + 0x18) +#define ESCREG_LOCALTIME 0x0910 +#define ESCREG_LOCALTIME_OFFSET 0x0920 +#define ESCREG_SYNC_ACT 0x0981 +#define ESCREG_SYNC_ACT_ACTIVATED 0x01 +#define ESCREG_SYNC_SYNC0_EN 0x02 +#define ESCREG_SYNC_SYNC1_EN 0x04 +#define ESCREG_SYNC_AUTO_ACTIVATED 0x08 +#define ESCREG_SYNC0_CYCLE_TIME 0x09A0 +#define ESCREG_SYNC1_CYCLE_TIME 0x09A4 +#define ESCREG_SMENABLE_BIT 0x01 +#define ESCREG_AL_STATEMASK 0x001f +#define ESCREG_AL_ALLBUTINITMASK 0x0e +#define ESCREG_AL_ERRACKMASK 0x0f + +#define SYNCTYPE_SUPPORT_FREERUN 0x01 +#define SYNCTYPE_SUPPORT_SYNCHRON 0x02 +#define SYNCTYPE_SUPPORT_DCSYNC0 0x04 +#define SYNCTYPE_SUPPORT_DCSYNC1 0x08 +#define SYNCTYPE_SUPPORT_SUBCYCLE 0x10 + +#define ESCinit 0x01 +#define ESCpreop 0x02 +#define ESCboot 0x03 +#define ESCsafeop 0x04 +#define ESCop 0x08 +#define ESCerror 0x10 + +#define INIT_TO_INIT 0x11 +#define INIT_TO_PREOP 0x21 +#define INIT_TO_BOOT 0x31 +#define INIT_TO_SAFEOP 0x41 +#define INIT_TO_OP 0x81 +#define PREOP_TO_INIT 0x12 +#define PREOP_TO_PREOP 0x22 +#define PREOP_TO_BOOT 0x32 +#define PREOP_TO_SAFEOP 0x42 +#define PREOP_TO_OP 0x82 +#define BOOT_TO_INIT 0x13 +#define BOOT_TO_PREOP 0x23 +#define BOOT_TO_BOOT 0x33 +#define BOOT_TO_SAFEOP 0x43 +#define BOOT_TO_OP 0x83 +#define SAFEOP_TO_INIT 0x14 +#define SAFEOP_TO_PREOP 0x24 +#define SAFEOP_TO_BOOT 0x34 +#define SAFEOP_TO_SAFEOP 0x44 +#define SAFEOP_TO_OP 0x84 +#define OP_TO_INIT 0x18 +#define OP_TO_PREOP 0x28 +#define OP_TO_BOOT 0x38 +#define OP_TO_SAFEOP 0x48 +#define OP_TO_OP 0x88 + +#define ALERR_NONE 0x0000 +#define ALERR_UNSPECIFIEDERROR 0x0001 +#define ALERR_NOMEMORY 0x0002 +#define ALERR_INVALIDSTATECHANGE 0x0011 +#define ALERR_UNKNOWNSTATE 0x0012 +#define ALERR_BOOTNOTSUPPORTED 0x0013 +#define ALERR_NOVALIDFIRMWARE 0x0014 +#define ALERR_INVALIDBOOTMBXCONFIG 0x0015 +#define ALERR_INVALIDMBXCONFIG 0x0016 +#define ALERR_INVALIDSMCONFIG 0x0017 +#define ALERR_NOVALIDINPUTS 0x0018 +#define ALERR_NOVALIDOUTPUTS 0x0019 +#define ALERR_SYNCERROR 0x001A +#define ALERR_WATCHDOG 0x001B +#define ALERR_INVALIDSYNCMANAGERTYP 0x001C +#define ALERR_INVALIDOUTPUTSM 0x001D +#define ALERR_INVALIDINPUTSM 0x001E +#define ALERR_INVALIDWDTCFG 0x001F +#define ALERR_SLAVENEEDSCOLDSTART 0x0020 +#define ALERR_SLAVENEEDSINIT 0x0021 +#define ALERR_SLAVENEEDSPREOP 0x0022 +#define ALERR_SLAVENEEDSSAFEOP 0x0023 +#define ALERR_INVALIDINPUTMAPPING 0x0024 +#define ALERR_INVALIDOUTPUTMAPPING 0x0025 +#define ALERR_INCONSISTENTSETTINGS 0x0026 +#define ALERR_FREERUNNOTSUPPORTED 0x0027 +#define ALERR_SYNCNOTSUPPORTED 0x0028 +#define ALERR_FREERUNNEEDS3BUFFMODE 0x0029 +#define ALERR_BACKGROUNDWATCHDOG 0x002A +#define ALERR_NOVALIDINPUTSOUTPUTS 0x002B +#define ALERR_FATALSYNCERROR 0x002C +#define ALERR_NOSYNCERROR 0x002D +#define ALERR_INVALIDINPUTFMMUCFG 0x002E +#define ALERR_DCINVALIDSYNCCFG 0x0030 +#define ALERR_INVALIDDCLATCHCFG 0x0031 +#define ALERR_PLLERROR 0x0032 +#define ALERR_DCSYNCIOERROR 0x0033 +#define ALERR_DCSYNCTIMEOUT 0x0034 +#define ALERR_DCSYNCCYCLETIME 0x0035 +#define ALERR_DCSYNC0CYCLETIME 0x0036 +#define ALERR_DCSYNC1CYCLETIME 0x0037 +#define ALERR_MBXAOE 0x0041 +#define ALERR_MBXEOE 0x0042 +#define ALERR_MBXCOE 0x0043 +#define ALERR_MBXFOE 0x0044 +#define ALERR_MBXSOE 0x0045 +#define ALERR_MBXVOE 0x004F +#define ALERR_EEPROMNOACCESS 0x0050 +#define ALERR_EEPROMERROR 0x0051 +#define ALERR_SLAVERESTARTEDLOCALLY 0x0060 +#define ALERR_DEVICEIDVALUEUPDATED 0x0061 +#define ALERR_APPLCTRLAVAILABLE 0x00f0 +#define ALERR_UNKNOWN 0xffff + +#define MBXERR_SYNTAX 0x0001 +#define MBXERR_UNSUPPORTEDPROTOCOL 0x0002 +#define MBXERR_INVALIDCHANNEL 0x0003 +#define MBXERR_SERVICENOTSUPPORTED 0x0004 +#define MBXERR_INVALIDHEADER 0x0005 +#define MBXERR_SIZETOOSHORT 0x0006 +#define MBXERR_NOMOREMEMORY 0x0007 +#define MBXERR_INVALIDSIZE 0x0008 + +#define ABORT_NOTOGGLE 0x05030000 +#define ABORT_TRANSFER_TIMEOUT 0x05040000 +#define ABORT_UNKNOWN 0x05040001 +#define ABORT_INVALID_BLOCK_SIZE 0x05040002 +#define ABORT_INVALID_SEQUENCE_NUMBER 0x05040003 +#define ABORT_BLOCK_CRC_ERROR 0x05040004 +#define ABORT_OUT_OF_MEMORY 0x05040005 +#define ABORT_UNSUPPORTED 0x06010000 +#define ABORT_WRITEONLY 0x06010001 +#define ABORT_READONLY 0x06010002 +#define ABORT_SUBINDEX0_NOT_ZERO 0x06010003 +#define ABORT_CA_NOT_SUPPORTED 0x06010004 +#define ABORT_EXCEEDS_MBOX_SIZE 0x06010005 +#define ABORT_SDO_DOWNLOAD_BLOCKED 0x06010006 +#define ABORT_NOOBJECT 0x06020000 +#define ABORT_MAPPING_OBJECT_ERROR 0x06040041 +#define ABORT_MAPPING_LENGTH_ERROR 0x06040042 +#define ABORT_GENERAL_PARAMETER_ERROR 0x06040043 +#define ABORT_GENERAL_DEVICE_ERROR 0x06040047 +#define ABORT_HARDWARE_ERROR 0x06060000 +#define ABORT_TYPEMISMATCH 0x06070010 +#define ABORT_DATATYPE_TOO_HIGH 0x06070012 +#define ABORT_DATATYPE_TOO_LOW 0x06070013 +#define ABORT_NOSUBINDEX 0x06090011 +#define ABORT_VALUE_EXCEEDED 0x06090030 +#define ABORT_VALUE_TOO_HIGH 0x06090031 +#define ABORT_VALUE_TOO_LOW 0x06090032 +#define ABORT_MODULE_LIST_MISMATCH 0x06090033 +#define ABORT_MAX_VAL_LESS_THAN_MIN_VAL 0x06090036 +#define ABORT_RESOURCE_NOT_AVAILABLE 0x060A0023 +#define ABORT_GENERALERROR 0x08000000 +#define ABORT_DATA_STORE_ERROR 0x08000020 +#define ABORT_DATA_STORE_LOCAL_ERROR 0x08000021 +#define ABORT_NOTINTHISSTATE 0x08000022 +#define ABORT_OBJECT_DICTIONARY_ERROR 0x08000023 +#define ABORT_NO_DATA_AVAILABLE 0x08000024 + +#define MBXstate_idle 0x00 +#define MBXstate_inclaim 0x01 +#define MBXstate_outclaim 0x02 +#define MBXstate_outreq 0x03 +#define MBXstate_outpost 0x04 +#define MBXstate_backup 0x05 +#define MBXstate_again 0x06 + +#define COE_DEFAULTLENGTH 0x0a +#define COE_HEADERSIZE 0x0a +#define COE_SEGMENTHEADERSIZE 0x03 +#define COE_SDOREQUEST 0x02 +#define COE_SDORESPONSE 0x03 +#define COE_SDOINFORMATION 0x08 +#define COE_COMMAND_SDOABORT 0x80 +#define COE_COMMAND_UPLOADREQUEST 0x40 +#define COE_COMMAND_UPLOADRESPONSE 0x40 +#define COE_COMMAND_UPLOADSEGMENT 0x00 +#define COE_COMMAND_UPLOADSEGREQ 0x60 +#define COE_COMMAND_DOWNLOADREQUEST 0x20 +#define COE_COMMAND_DOWNLOADRESPONSE 0x60 +#define COE_COMMAND_DOWNLOADSEGREQ 0x00 +#define COE_COMMAND_DOWNLOADSEGRESP 0x20 +#define COE_COMMAND_LASTSEGMENTBIT 0x01 +#define COE_SIZE_INDICATOR 0x01 +#define COE_EXPEDITED_INDICATOR 0x02 +#define COE_COMPLETEACCESS 0x10 +#define COE_TOGGLEBIT 0x10 +#define COE_INFOERROR 0x07 +#define COE_GETODLISTRESPONSE 0x02 +#define COE_GETODRESPONSE 0x04 +#define COE_ENTRYDESCRIPTIONRESPONSE 0x06 +#define COE_VALUEINFO_ACCESS 0x01 +#define COE_VALUEINFO_OBJECT 0x02 +#define COE_VALUEINFO_MAPPABLE 0x04 +#define COE_VALUEINFO_TYPE 0x08 +#define COE_VALUEINFO_DEFAULT 0x10 +#define COE_VALUEINFO_MINIMUM 0x20 +#define COE_VALUEINFO_MAXIMUM 0x40 +#define COE_MINIMUM_LENGTH 8 + +#define MBXERR 0x00 +#define MBXAOE 0x01 +#define MBXEOE 0x02 +#define MBXCOE 0x03 +#define MBXFOE 0x04 +#define MBXODL 0x10 +#define MBXOD 0x20 +#define MBXED 0x30 +#define MBXSEU 0x40 +#define MBXSED 0x50 + +#define SMRESULT_ERRSM0 0x01 +#define SMRESULT_ERRSM1 0x02 +#define SMRESULT_ERRSM2 0x04 +#define SMRESULT_ERRSM3 0x08 + +#define FOE_ERR_NOTDEFINED 0x8000 +#define FOE_ERR_NOTFOUND 0x8001 +#define FOE_ERR_ACCESS 0x8002 +#define FOE_ERR_DISKFULL 0x8003 +#define FOE_ERR_ILLEGAL 0x8004 +#define FOE_ERR_PACKETNO 0x8005 +#define FOE_ERR_EXISTS 0x8006 +#define FOE_ERR_NOUSER 0x8007 +#define FOE_ERR_BOOTSTRAPONLY 0x8008 +#define FOE_ERR_NOTINBOOTSTRAP 0x8009 +#define FOE_ERR_NORIGHTS 0x800A +#define FOE_ERR_PROGERROR 0x800B +#define FOE_ERR_CHECKSUM 0x800C + +#define FOE_OP_RRQ 1 +#define FOE_OP_WRQ 2 +#define FOE_OP_DATA 3 +#define FOE_OP_ACK 4 +#define FOE_OP_ERR 5 +#define FOE_OP_BUSY 6 + +#define FOE_READY 0 +#define FOE_WAIT_FOR_ACK 1 +#define FOE_WAIT_FOR_FINAL_ACK 2 +#define FOE_WAIT_FOR_DATA 3 + +#define EOE_RESULT_SUCCESS 0x0000 +#define EOE_RESULT_UNSPECIFIED_ERROR 0x0001 +#define EOE_RESULT_UNSUPPORTED_FRAME_TYPE 0x0002 +#define EOE_RESULT_NO_IP_SUPPORT 0x0201 +#define EOE_RESULT_NO_DHCP_SUPPORT 0x0202 +#define EOE_RESULT_NO_FILTER_SUPPORT 0x0401 + +#define APPSTATE_IDLE 0x00 +#define APPSTATE_INPUT 0x01 +#define APPSTATE_OUTPUT 0x02 + +#define PREALLOC_FACTOR 3 +#define PREALLOC_BUFFER_SIZE (PREALLOC_FACTOR * MBXSIZE) + +typedef struct sm_cfg +{ + uint16_t cfg_sma; + uint16_t cfg_sml; + uint16_t cfg_sme; + uint8_t cfg_smc; + uint8_t cfg_smact; +}sm_cfg_t; + +typedef struct esc_cfg +{ + void * user_arg; + int use_interrupt; + int watchdog_cnt; + bool skip_default_initialization; + void (*set_defaults_hook) (void); + void (*pre_state_change_hook) (uint8_t * as, uint8_t * an); + void (*post_state_change_hook) (uint8_t * as, uint8_t * an); + void (*application_hook) (void); + void (*safeoutput_override) (void); + uint32_t (*pre_object_download_hook) (uint16_t index, + uint8_t subindex, + void * data, + size_t size, + uint16_t flags); + uint32_t (*post_object_download_hook) (uint16_t index, + uint8_t subindex, + uint16_t flags); + uint32_t (*pre_object_upload_hook) (uint16_t index, + uint8_t subindex, + void * data, + size_t size, + uint16_t flags); + uint32_t (*post_object_upload_hook) (uint16_t index, + uint8_t subindex, + uint16_t flags); + void (*rxpdo_override) (void); + void (*txpdo_override) (void); + void (*esc_hw_interrupt_enable) (uint32_t mask); + void (*esc_hw_interrupt_disable) (uint32_t mask); + void (*esc_hw_eep_handler) (void); + uint16_t (*esc_check_dc_handler) (void); +} esc_cfg_t; + +typedef struct +{ + uint8_t state; +} _App; + +// Attention! this struct is always little-endian +CC_PACKED_BEGIN +typedef struct CC_PACKED +{ + uint16_t PSA; + uint16_t Length; + +#if defined(EC_LITTLE_ENDIAN) + uint8_t Mode:2; + uint8_t Direction:2; + uint8_t IntECAT:1; + uint8_t IntPDI:1; + uint8_t WTE:1; + uint8_t R1:1; + + uint8_t IntW:1; + uint8_t IntR:1; + uint8_t R2:1; + uint8_t MBXstat:1; + uint8_t BUFstat:2; + uint8_t R3:2; + + uint8_t ECsm:1; + uint8_t ECrep:1; + uint8_t ECr4:4; + uint8_t EClatchEC:1; + uint8_t EClatchPDI:1; + + uint8_t PDIsm:1; + uint8_t PDIrep:1; + uint8_t PDIr5:6; +#endif + +#if defined(EC_BIG_ENDIAN) + uint8_t R1:1; + uint8_t WTE:1; + uint8_t IntPDI:1; + uint8_t IntECAT:1; + uint8_t Direction:2; + uint8_t Mode:2; + + uint8_t R3:2; + uint8_t BUFstat:2; + uint8_t MBXstat:1; + uint8_t R2:1; + uint8_t IntR:1; + uint8_t IntW:1; + + uint8_t EClatchPDI:1; + uint8_t EClatchEC:1; + uint8_t ECr4:4; + uint8_t ECrep:1; + uint8_t ECsm:1; + + uint8_t PDIr5:6; + uint8_t PDIrep:1; + uint8_t PDIsm:1; +#endif +} _ESCsm; +CC_PACKED_END + +/* Attention! this struct is always little-endian */ +CC_PACKED_BEGIN +typedef struct CC_PACKED +{ + uint16_t PSA; + uint16_t Length; + uint8_t Command; + uint8_t Status; + uint8_t ActESC; + uint8_t ActPDI; +} _ESCsm2; +CC_PACKED_END + +CC_PACKED_BEGIN +typedef struct CC_PACKED +{ + uint16_t PSA; + uint16_t Length; + uint8_t Command; +} _ESCsmCompact; +CC_PACKED_END + +typedef struct +{ + /* Configuration input is saved so the user variable may go out-of-scope */ + int use_interrupt; + sm_cfg_t mb[2]; + sm_cfg_t mbboot[2]; + bool skip_default_initialization; + void (*set_defaults_hook) (void); + void (*pre_state_change_hook) (uint8_t * as, uint8_t * an); + void (*post_state_change_hook) (uint8_t * as, uint8_t * an); + void (*application_hook) (void); + void (*safeoutput_override) (void); + uint32_t (*pre_object_download_hook) (uint16_t index, + uint8_t subindex, + void * data, + size_t size, + uint16_t flags); + uint32_t (*post_object_download_hook) (uint16_t index, + uint8_t subindex, + uint16_t flags); + uint32_t (*pre_object_upload_hook) (uint16_t index, + uint8_t subindex, + void * data, + size_t size, + uint16_t flags); + uint32_t (*post_object_upload_hook) (uint16_t index, + uint8_t subindex, + uint16_t flags); + void (*rxpdo_override) (void); + void (*txpdo_override) (void); + void (*esc_hw_interrupt_enable) (uint32_t mask); + void (*esc_hw_interrupt_disable) (uint32_t mask); + void (*esc_hw_eep_handler) (void); + uint16_t (*esc_check_dc_handler) (void); + uint8_t MBXrun; + size_t activembxsize; + sm_cfg_t * activemb0; + sm_cfg_t * activemb1; + uint16_t ESC_SM2_sml; + uint16_t ESC_SM3_sml; + uint8_t dcsync; + uint16_t synccounterlimit; + uint16_t ALstatus; + uint16_t ALcontrol; + uint16_t ALerror; + uint16_t DLstatus; + uint16_t address; + uint8_t mbxcnt; + uint8_t mbxincnt; + uint8_t mbxoutpost; + uint8_t mbxbackup; + uint8_t xoe; + uint8_t txcue; + uint8_t mbxfree; + uint8_t segmented; + void *data; + uint16_t entries; + uint16_t frags; + uint16_t fragsleft; + uint16_t index; + uint8_t subindex; + uint16_t flags; + + uint8_t toggle; + + int sm2mappings; + int sm3mappings; + + uint8_t SMtestresult; + uint32_t PrevTime; + _ESCsm SM[4]; + /* Volatile since it may be read from ISR */ + volatile int watchdogcnt; + volatile uint32_t Time; + volatile uint16_t ALevent; + volatile int8_t synccounter; + volatile _App App; + uint8_t mbxdata[PREALLOC_BUFFER_SIZE]; +} _ESCvar; + +CC_PACKED_BEGIN +typedef struct CC_PACKED +{ + uint16_t length; + uint16_t address; + +#if defined(EC_LITTLE_ENDIAN) + uint8_t channel:6; + uint8_t priority:2; + + uint8_t mbxtype:4; + uint8_t mbxcnt:4; +#endif + +#if defined(EC_BIG_ENDIAN) + uint8_t priority:2; + uint8_t channel:6; + + uint8_t mbxcnt:4; + uint8_t mbxtype:4; +#endif +} _MBXh; +CC_PACKED_END + +CC_PACKED_BEGIN +typedef struct CC_PACKED +{ + _MBXh header; + uint8_t b[0]; +} _MBX; +CC_PACKED_END + +CC_PACKED_BEGIN +typedef struct CC_PACKED +{ + uint16_t numberservice; +} _COEh; +CC_PACKED_END + +CC_PACKED_BEGIN +typedef struct CC_PACKED +{ +#if defined(EC_LITTLE_ENDIAN) + uint8_t opcode:7; + uint8_t incomplete:1; +#endif + +#if defined(EC_BIG_ENDIAN) + uint8_t incomplete:1; + uint8_t opcode:7; +#endif + + uint8_t reserved; + uint16_t fragmentsleft; +} _INFOh; +CC_PACKED_END + +CC_PACKED_BEGIN +typedef struct CC_PACKED +{ + _MBXh mbxheader; + uint16_t type; + uint16_t detail; +} _MBXerr; +CC_PACKED_END + +CC_PACKED_BEGIN +typedef struct CC_PACKED +{ + _MBXh mbxheader; + _COEh coeheader; + uint8_t command; + uint16_t index; + uint8_t subindex; + uint32_t size; +} _COEsdo; +CC_PACKED_END + +CC_PACKED_BEGIN +typedef struct CC_PACKED +{ + _MBXh mbxheader; + _COEh coeheader; + _INFOh infoheader; + uint16_t index; + uint16_t datatype; + uint8_t maxsub; + uint8_t objectcode; + char name; +} _COEobjdesc; +CC_PACKED_END + +CC_PACKED_BEGIN +typedef struct CC_PACKED +{ + _MBXh mbxheader; + _COEh coeheader; + _INFOh infoheader; + uint16_t index; + uint8_t subindex; + uint8_t valueinfo; + uint16_t datatype; + uint16_t bitlength; + uint16_t access; + char name; +} _COEentdesc; +CC_PACKED_END + +CC_PACKED_BEGIN +typedef struct CC_PACKED +{ + uint8_t opcode; + uint8_t reserved; + union + { + uint32_t password; + uint32_t packetnumber; + uint32_t errorcode; + }; +} _FOEh; +CC_PACKED_END + +CC_PACKED_BEGIN +typedef struct CC_PACKED +{ + _MBXh mbxheader; + _FOEh foeheader; + union + { + char filename[0]; + uint8_t data[0]; + char errortext[0]; + }; +} _FOE; +CC_PACKED_END + +CC_PACKED_BEGIN +typedef struct CC_PACKED +{ + uint16_t frameinfo1; + union + { + uint16_t frameinfo2; + uint16_t result; + }; +} _EOEh; +CC_PACKED_END + +CC_PACKED_BEGIN +typedef struct CC_PACKED +{ + _MBXh mbxheader; + _EOEh eoeheader; + uint8_t data[0]; +} _EOE; +CC_PACKED_END + +/* state definition in mailbox + * 0 : idle + * 1 : claimed for inbox + * 2 : claimed for outbox + * 3 : request post outbox + * 4 : outbox posted not send + * 5 : backup outbox + * 6 : mailbox needs to be transmitted again + */ +typedef struct +{ + uint8_t state; +} _MBXcontrol; + +/* Stack reference to application configuration of the ESC */ +#define ESC_MBXSIZE (ESCvar.activembxsize) +#define ESC_MBX0_sma (ESCvar.activemb0->cfg_sma) +#define ESC_MBX0_sml (ESCvar.activemb0->cfg_sml) +#define ESC_MBX0_sme (ESCvar.activemb0->cfg_sme) +#define ESC_MBX0_smc (ESCvar.activemb0->cfg_smc) +#define ESC_MBX1_sma (ESCvar.activemb1->cfg_sma) +#define ESC_MBX1_sml (ESCvar.activemb1->cfg_sml) +#define ESC_MBX1_sme (ESCvar.activemb1->cfg_sme) +#define ESC_MBX1_smc (ESCvar.activemb1->cfg_smc) +#define ESC_MBXBUFFERS (MBXBUFFERS) +#define ESC_SM2_sma (SM2_sma) +#define ESC_SM2_smc (SM2_smc) +#define ESC_SM2_act (SM2_act) +#define ESC_SM3_sma (SM3_sma) +#define ESC_SM3_smc (SM3_smc) +#define ESC_SM3_act (SM3_act) + +#define ESC_MBXHSIZE sizeof(_MBXh) +#define ESC_MBXDSIZE (ESC_MBXSIZE - ESC_MBXHSIZE) +#define ESC_FOEHSIZE sizeof(_FOEh) +#define ESC_FOE_DATA_SIZE (ESC_MBXSIZE - (ESC_MBXHSIZE +ESC_FOEHSIZE)) +#define ESC_EOEHSIZE sizeof(_EOEh) +#define ESC_EOE_DATA_SIZE (ESC_MBXSIZE - (ESC_MBXHSIZE +ESC_EOEHSIZE)) + +void ESC_config (esc_cfg_t * cfg); +void ESC_ALerror (uint16_t errornumber); +void ESC_ALeventwrite (uint32_t event); +uint32_t ESC_ALeventread (void); +void ESC_ALeventmaskwrite (uint32_t mask); +uint32_t ESC_ALeventmaskread (void); +void ESC_ALstatus (uint8_t status); +void ESC_ALstatusgotoerror (uint8_t status, uint16_t errornumber); +void ESC_SMstatus (uint8_t n); +uint8_t ESC_WDstatus (void); +uint8_t ESC_claimbuffer (void); +uint8_t ESC_startmbx (uint8_t state); +void ESC_stopmbx (void); +void MBX_error (uint16_t error); +uint8_t ESC_mbxprocess (void); +void ESC_xoeprocess (void); +uint8_t ESC_startinput (uint8_t state); +void ESC_stopinput (void); +uint8_t ESC_startoutput (uint8_t state); +void ESC_stopoutput (void); +void ESC_state (void); +void ESC_sm_act_event (void); + +/* From hardware file */ +void ESC_read (uint16_t address, void *buf, uint16_t len); +void ESC_write (uint16_t address, void *buf, uint16_t len); +void ESC_init (const esc_cfg_t * cfg); +void ESC_reset (void); + +/* From application */ +extern void APP_safeoutput (); +extern _ESCvar ESCvar; +extern _MBXcontrol MBXcontrol[]; +extern uint8_t MBX[]; +extern _SMmap SMmap2[]; +extern _SMmap SMmap3[]; + +/* ATOMIC operations are used when running interrupt driven */ +#ifndef CC_ATOMIC_SET +#define CC_ATOMIC_SET(var,val) (var = val) +#endif + +#ifndef CC_ATOMIC_GET +#define CC_ATOMIC_GET(var) (var) +#endif + +#ifndef CC_ATOMIC_ADD +#define CC_ATOMIC_ADD(var,val) (var += val) +#endif + +#ifndef CC_ATOMIC_SUB +#define CC_ATOMIC_SUB(var,val) (var -= val) +#endif + +#ifndef CC_ATOMIC_AND +#define CC_ATOMIC_AND(var,val) (var &= val) +#endif + +#ifndef CC_ATOMIC_OR +#define CC_ATOMIC_OR(var,val) (var |= val) +#endif + + +#endif diff --git a/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/esc_coe.c b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/esc_coe.c new file mode 100755 index 0000000..58fe954 --- /dev/null +++ b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/esc_coe.c @@ -0,0 +1,1783 @@ +/* + * Licensed under the GNU General Public License version 2 with exceptions. See + * LICENSE file in the project root for full license information + */ + + /** \file + * \brief + * CAN over EtherCAT (CoE) module. + * + * SDO read / write and SDO service functions + */ + +#include +#include +#include +#include +#include +#include "esc.h" +#include "esc_coe.h" + +#define BITS2BYTES(b) ((b + 7) >> 3) + +/* Fetch value from object dictionary */ +#define OBJ_VALUE_FETCH(v, o) \ + ((o).data ? *(__typeof__ (v) *)(o).data : (__typeof__ (v))(o).value) + +#define SDO_COMMAND(byte) \ + (byte & 0xe0) +#define SDO_COMPLETE_ACCESS(byte) \ + ((byte & COE_COMPLETEACCESS) == COE_COMPLETEACCESS) + +#define READ_ACCESS(access, state) \ + (((access & ATYPE_Rpre) && (state == ESCpreop)) || \ + ((access & ATYPE_Rsafe) && (state == ESCsafeop)) || \ + ((access & ATYPE_Rop) && (state == ESCop))) + +#define WRITE_ACCESS(access, state) \ + (((access & ATYPE_Wpre) && (state == ESCpreop)) || \ + ((access & ATYPE_Wsafe) && (state == ESCsafeop)) || \ + ((access & ATYPE_Wop) && (state == ESCop))) + +typedef enum { UPLOAD, DOWNLOAD } load_t; + +/** Search for an object sub-index. + * + * @param[in] nidx = local array index of object we want to find sub-index to + * @param[in] subindex = value on sub-index of object we want to locate + * @return local array index if we succeed, -1 if we didn't find the index. + */ +int16_t SDO_findsubindex (int16_t nidx, uint8_t subindex) +{ + const _objd *objd; + int16_t n = 0; + uint8_t maxsub; + objd = SDOobjects[nidx].objdesc; + maxsub = SDOobjects[nidx].maxsub; + + /* Since most objects contain all subindexes (i.e. are not sparse), + * check the most likely scenario first + */ + if ((subindex <= maxsub) && ((objd + subindex)->subindex == subindex)) + { + return subindex; + } + + while (((objd + n)->subindex < subindex) && (n < maxsub)) + { + n++; + } + if ((objd + n)->subindex != subindex) + { + return -1; + } + return n; +} + +/** Search for an object index matching the wanted value in the Object List. + * + * @param[in] index = value on index of object we want to locate + * @return local array index if we succeed, -1 if we didn't find the index. + */ +int32_t SDO_findobject (uint16_t index) +{ + int32_t n = 0; + while (SDOobjects[n].index < index) + { + n++; + } + if (SDOobjects[n].index != index) + { + return -1; + } + return n; +} + +/** + * Calculate the size in Bytes of RxPDO or TxPDOs by adding the + * objects in SyncManager SDO 1C1x. + * + * A list of mapped objects is created for fast lookup of + * dynamically mapped process data. The max size of the list (@a + * max_mappings) can be set to 0 if dynamic processdata is not + * supported. + * + * The output variable @a nmappings is set to 0 if dynamic processdata + * is not supported. It is set to the number of mapped objects if + * dynamic processdata is supported, or -1 if the mapping was + * incorrect. + + * @param[in] index = SM index + * @param[out] nmappings = number of mapped objects in SM, or -1 if + * mapping is invalid + * @param[out] mappings = list of mapped objects in SM + * @param[out] max_mappings = max number of mapped objects in SM + * @return size of RxPDO or TxPDOs in Bytes. + */ +uint16_t sizeOfPDO (uint16_t index, int * nmappings, _SMmap * mappings, + int max_mappings) +{ + uint16_t offset = 0, hobj; + uint8_t si, sic, c; + int16_t nidx; + const _objd *objd; + const _objd *objd1c1x; + int mapIx = 0; + + if ((index != RX_PDO_OBJIDX) && (index != TX_PDO_OBJIDX)) + { + return 0; + } + + nidx = SDO_findobject (index); + if(nidx < 0) + { + return 0; + } + + objd1c1x = SDOobjects[nidx].objdesc; + + si = OBJ_VALUE_FETCH (si, objd1c1x[0]); + if (si) + { + for (sic = 1; sic <= si; sic++) + { + hobj = OBJ_VALUE_FETCH (hobj, objd1c1x[sic]); + nidx = SDO_findobject (hobj); + if (nidx >= 0) + { + uint8_t maxsub; + + objd = SDOobjects[nidx].objdesc; + maxsub = OBJ_VALUE_FETCH (maxsub, objd[0]); + + for (c = 1; c <= maxsub; c++) + { + uint32_t value = OBJ_VALUE_FETCH (value, objd[c]); + uint8_t bitlength = value & 0xFF; + + if (max_mappings > 0) + { + uint16_t index = value >> 16; + uint8_t subindex = (value >> 8) & 0xFF; + const _objd * mapping; + + if (mapIx == max_mappings) + { + /* Too many mapped objects */ + *nmappings = -1; + return 0; + } + + DPRINT ("%04x:%02x @ %d\n", index, subindex, offset); + + if (index == 0 && subindex == 0) + { + /* Padding element */ + mapping = NULL; + } + else + { + nidx = SDO_findobject (index); + if (nidx >= 0) + { + int16_t nsub; + + nsub = SDO_findsubindex (nidx, subindex); + if (nsub < 0) + { + /* Mapped subindex does not exist */ + *nmappings = -1; + return 0; + } + + mapping = &SDOobjects[nidx].objdesc[nsub]; + } + else + { + /* Mapped index does not exist */ + *nmappings = -1; + return 0; + } + } + + mappings[mapIx].obj = mapping; + mappings[mapIx++].offset = offset; + } + + offset += bitlength; + } + } + } + } + + if (max_mappings > 0) + { + *nmappings = mapIx; + } + else + { + *nmappings = 0; + } + + return BITS2BYTES (offset); +} + +/** Copy to mailbox. + * + * @param[in] source = pointer to source + * @param[in] dest = pointer to destination + * @param[in] size = Size to copy + */ +static void copy2mbx (void *source, void *dest, uint16_t size) +{ + memcpy (dest, source, size); +} + +/** Function for sending an SDO Abort reply. + * + * @param[in] index = index of object causing abort reply + * @param[in] sub-index = sub-index of object causing abort reply + * @param[in] abortcode = abort code to send in reply + */ +void SDO_abort (uint16_t index, uint8_t subindex, uint32_t abortcode) +{ + uint8_t MBXout; + _COEsdo *coeres; + MBXout = ESC_claimbuffer (); + if (MBXout) + { + coeres = (_COEsdo *) &MBX[MBXout * ESC_MBXSIZE]; + coeres->mbxheader.length = htoes (COE_DEFAULTLENGTH); + coeres->mbxheader.mbxtype = MBXCOE; + coeres->coeheader.numberservice = + htoes ((0 & 0x01f) | (COE_SDOREQUEST << 12)); + coeres->index = htoes (index); + coeres->subindex = subindex; + coeres->command = COE_COMMAND_SDOABORT; + coeres->size = htoel (abortcode); + MBXcontrol[MBXout].state = MBXstate_outreq; + } +} + +static void set_state_idle(uint16_t index, + uint8_t subindex, + uint32_t abortcode) +{ + if (abortcode != 0) + { + SDO_abort (index, subindex, abortcode); + } + + MBXcontrol[0].state = MBXstate_idle; + ESCvar.xoe = 0; +} + +/** Function for responding on requested SDO Upload, sending the content + * requested in a free Mailbox buffer. Depending of size of data expedited, + * normal or segmented transfer is used. On error an SDO Abort will be sent. + */ +static void SDO_upload (void) +{ + _COEsdo *coesdo, *coeres; + uint16_t index; + uint8_t subindex; + int16_t nidx, nsub; + uint8_t MBXout; + uint32_t size; + uint8_t dss; + uint32_t abort = 1; + const _objd *objd; + coesdo = (_COEsdo *) &MBX[0]; + index = etohs (coesdo->index); + subindex = coesdo->subindex; + nidx = SDO_findobject (index); + if (nidx >= 0) + { + nsub = SDO_findsubindex (nidx, subindex); + if (nsub >= 0) + { + objd = SDOobjects[nidx].objdesc; + uint8_t access = (objd + nsub)->flags & 0x3f; + uint8_t state = ESCvar.ALstatus & 0x0f; + if (!READ_ACCESS(access, state)) + { + set_state_idle (index, subindex, ABORT_WRITEONLY); + return; + } + MBXout = ESC_claimbuffer (); + if (MBXout) + { + coeres = (_COEsdo *) &MBX[MBXout * ESC_MBXSIZE]; + coeres->mbxheader.length = htoes (COE_DEFAULTLENGTH); + coeres->mbxheader.mbxtype = MBXCOE; + coeres->coeheader.numberservice = + htoes ((0 & 0x01f) | (COE_SDORESPONSE << 12)); + size = (objd + nsub)->bitlength; + /* expedited bits used calculation */ + dss = 0x0c; + if (size > 8) + { + dss = 0x08; + } + if (size > 16) + { + dss = 0x04; + } + if (size > 24) + { + dss = 0x00; + } + coeres->index = htoes (index); + coeres->subindex = subindex; + if (size <= 32) + { + /* expedited response i.e. length<=4 bytes */ + coeres->command = COE_COMMAND_UPLOADRESPONSE + + COE_SIZE_INDICATOR + COE_EXPEDITED_INDICATOR + dss; + /* convert bits to bytes */ + size = BITS2BYTES(size); + void *dataptr = ((objd + nsub)->data) ? + (objd + nsub)->data : (void *)&((objd + nsub)->value); + abort = ESC_upload_pre_objecthandler (index, subindex, + dataptr, size, (objd + nsub)->flags); + if (abort == 0) + { + if ((objd + nsub)->data == NULL) + { + /* use constant value */ + coeres->size = htoel ((objd + nsub)->value); + } + else + { + /* use dynamic data */ + copy2mbx ((objd + nsub)->data, &(coeres->size), size); + } + } + else + { + SDO_abort (index, subindex, abort); + } + } + else + { + /* normal response i.e. length>4 bytes */ + coeres->command = COE_COMMAND_UPLOADRESPONSE + + COE_SIZE_INDICATOR; + /* convert bits to bytes */ + size = BITS2BYTES(size); + /* set total size in bytes */ + ESCvar.frags = size; + coeres->size = htoel (size); + if ((size + COE_HEADERSIZE) > ESC_MBXDSIZE) + { + /* segmented transfer needed */ + /* limit to mailbox size */ + size = ESC_MBXDSIZE - COE_HEADERSIZE; + /* number of bytes done */ + ESCvar.fragsleft = size; + /* signal segmented transfer */ + ESCvar.segmented = MBXSEU; + ESCvar.data = (objd + nsub)->data; + ESCvar.flags = (objd + nsub)->flags; + } + else + { + ESCvar.segmented = 0; + } + coeres->mbxheader.length = htoes (COE_HEADERSIZE + size); + abort = ESC_upload_pre_objecthandler (index, subindex, + (objd + nsub)->data, ESCvar.frags, (objd + nsub)->flags); + if (abort == 0) + { + /* use dynamic data */ + copy2mbx ((objd + nsub)->data, (&(coeres->size)) + 1, size); + } + else + { + SDO_abort (index, subindex, abort); + } + } + if ((abort == 0) && (ESCvar.segmented == 0)) + { + abort = ESC_upload_post_objecthandler (index, subindex, + (objd + nsub)->flags); + if (abort != 0) + { + SDO_abort (index, subindex, abort); + } + } + MBXcontrol[MBXout].state = MBXstate_outreq; + } + } + else + { + SDO_abort (index, subindex, ABORT_NOSUBINDEX); + } + } + else + { + SDO_abort (index, subindex, ABORT_NOOBJECT); + } + MBXcontrol[0].state = MBXstate_idle; + ESCvar.xoe = 0; +} + +static uint32_t complete_access_get_variables(_COEsdo *coesdo, uint16_t *index, + uint8_t *subindex, int16_t *nidx, + int16_t *nsub) +{ + *index = etohs (coesdo->index); + *subindex = coesdo->subindex; + + /* A Complete Access must start with Subindex 0 or Subindex 1 */ + if (*subindex > 1) + { + return ABORT_UNSUPPORTED; + } + + *nidx = SDO_findobject (*index); + if (*nidx < 0) + { + return ABORT_NOOBJECT; + } + + *nsub = SDO_findsubindex (*nidx, *subindex); + if (*nsub < 0) + { + return ABORT_NOSUBINDEX; + } + + return 0; +} + +static uint32_t complete_access_subindex_loop(const _objd *objd, + int16_t nidx, + int16_t nsub, + uint8_t *mbxdata, + load_t load_type, + uint16_t max_bytes) +{ + /* Objects with dynamic entries cannot be accessed with Complete Access */ + if ((objd->datatype == DTYPE_VISIBLE_STRING) || + (objd->datatype == DTYPE_OCTET_STRING) || + (objd->datatype == DTYPE_UNICODE_STRING)) + { + return ABORT_CA_NOT_SUPPORTED; + } + + uint32_t size = 0; + + while (nsub <= SDOobjects[nidx].maxsub) + { + uint8_t bitlen = (objd + nsub)->bitlength; + void *ul_source = ((objd + nsub)->data != NULL) ? + (objd + nsub)->data : (void *)&((objd + nsub)->value); + uint8_t bitoffset = size % 8; + uint8_t access = (objd + nsub)->flags & 0x3f; + uint8_t state = ESCvar.ALstatus & 0x0f; + + if ((bitlen % 8) == 0) + { + if (bitoffset != 0) + { + /* move on to next byte boundary */ + size += (8 - bitoffset); + } + if (mbxdata != NULL) + { + /* copy a non-bit data type to a byte boundary */ + if (load_type == UPLOAD) + { + if (READ_ACCESS(access, state)) + { + memcpy(&mbxdata[BITS2BYTES(size)], ul_source, + BITS2BYTES(bitlen)); + } + else + { + /* return zeroes for upload of WO objects */ + memset(&mbxdata[BITS2BYTES(size)], 0, BITS2BYTES(bitlen)); + } + } + /* download of RO objects shall be ignored */ + else if (WRITE_ACCESS(access, state)) + { + memcpy((objd + nsub)->data, &mbxdata[BITS2BYTES(size)], + BITS2BYTES(bitlen)); + } + } + } + else if ((load_type == UPLOAD) && (mbxdata != NULL)) + { + /* copy a bit data type into correct position */ + uint8_t bitmask = (1 << bitlen) - 1; + if (READ_ACCESS(access, state)) + { + mbxdata[BITS2BYTES(size)] |= + (*(uint8_t *)ul_source & bitmask) << bitoffset; + } + else + { + mbxdata[BITS2BYTES(size)] &= ~(bitmask << bitoffset); + } + } + + /* Subindex 0 is padded to 16 bit */ + size += (nsub == 0) ? 16 : bitlen; + nsub++; + + if ((max_bytes > 0) && (BITS2BYTES(size) >= max_bytes)) + { + break; + } + } + + return size; +} + +static void init_coesdo(_COEsdo *coesdo, + uint8_t sdoservice, + uint8_t command, + uint16_t index, + uint8_t subindex) +{ + coesdo->mbxheader.length = htoes(COE_DEFAULTLENGTH); + coesdo->mbxheader.mbxtype = MBXCOE; + coesdo->coeheader.numberservice = htoes(sdoservice << 12); + coesdo->command = command; + coesdo->index = htoes(index); + coesdo->subindex = subindex; +} + +/** Function for responding on requested SDO Upload with Complete Access, + * sending the content requested in a free Mailbox buffer. Depending of + * size of data expedited, normal or segmented transfer is used. + * On error an SDO Abort will be sent. + */ +static void SDO_upload_complete_access (void) +{ + _COEsdo *coesdo = (_COEsdo *) &MBX[0]; + uint16_t index; + uint8_t subindex; + int16_t nidx, nsub; + uint32_t abortcode = complete_access_get_variables + (coesdo, &index, &subindex, &nidx, &nsub); + if (abortcode != 0) + { + set_state_idle (index, subindex, abortcode); + return; + } + + uint8_t MBXout = ESC_claimbuffer (); + if (MBXout == 0) + { + /* It is a bad idea to call SDO_abort when ESC_claimbuffer fails, + * because SDO_abort will also call ESC_claimbuffer ... + */ + set_state_idle (index, subindex, 0); + return; + } + + const _objd *objd = SDOobjects[nidx].objdesc; + + /* loop through the subindexes to get the total size */ + uint32_t size = complete_access_subindex_loop(objd, nidx, nsub, NULL, UPLOAD, 0); + if (size > 0xffff) + { + /* 'size' is in this case actually an abort code */ + set_state_idle (index, subindex, size); + return; + } + + /* check that upload data fits in the preallocated buffer */ + if ((size + PREALLOC_FACTOR * COE_HEADERSIZE) > PREALLOC_BUFFER_SIZE) + { + set_state_idle (index, subindex, ABORT_GENERALERROR); + return; + } + + abortcode = ESC_upload_pre_objecthandler(index, subindex, + objd->data, BITS2BYTES(size), objd->flags | COMPLETE_ACCESS_FLAG); + if (abortcode != 0) + { + SDO_abort (index, subindex, abortcode); + } + + /* copy subindex data into the preallocated buffer */ + complete_access_subindex_loop(objd, nidx, nsub, ESCvar.mbxdata, UPLOAD, 0); + + _COEsdo *coeres = (_COEsdo *) &MBX[MBXout * ESC_MBXSIZE]; + init_coesdo(coeres, COE_SDORESPONSE, + COE_COMMAND_UPLOADRESPONSE | COE_COMPLETEACCESS | COE_SIZE_INDICATOR, + index, subindex); + + /* expedited bits used calculation */ + uint8_t dss = (size > 24) ? 0 : (4 * (3 - ((size - 1) >> 3))); + + /* convert bits to bytes */ + size = BITS2BYTES(size); + + ESCvar.segmented = 0; + + if (size <= 4) + { + /* expedited response, i.e. length <= 4 bytes */ + coeres->command |= (COE_EXPEDITED_INDICATOR | dss); + memcpy(&(coeres->size), ESCvar.mbxdata, size); + } + else + { + /* normal response, i.e. length > 4 bytes */ + coeres->size = htoel (size); + + if ((size + COE_HEADERSIZE) > ESC_MBXDSIZE) + { + /* segmented transfer needed */ + /* set total size in bytes */ + ESCvar.frags = size; + /* limit to mailbox size */ + size = ESC_MBXDSIZE - COE_HEADERSIZE; + /* number of bytes done */ + ESCvar.fragsleft = size; + /* signal segmented transfer */ + ESCvar.segmented = MBXSEU; + ESCvar.data = ESCvar.mbxdata; + ESCvar.flags = COMPLETE_ACCESS_FLAG; + } + + coeres->mbxheader.length = htoes (COE_HEADERSIZE + size); + memcpy((&(coeres->size)) + 1, ESCvar.mbxdata, size); + } + + if (ESCvar.segmented == 0) + { + abortcode = ESC_upload_post_objecthandler (index, subindex, + objd->flags | COMPLETE_ACCESS_FLAG); + + if (abortcode != 0) + { + SDO_abort (index, subindex, abortcode); + } + } + + MBXcontrol[MBXout].state = MBXstate_outreq; + + set_state_idle (index, subindex, 0); +} + +/** Function for handling the following SDO Upload if previous SDOUpload + * response was flagged it needed to be segmented. + */ +static void SDO_uploadsegment (void) +{ + _COEsdo *coesdo, *coeres; + uint8_t MBXout; + uint32_t size, offset, abort; + coesdo = (_COEsdo *) &MBX[0]; + MBXout = ESC_claimbuffer (); + if (MBXout) + { + coeres = (_COEsdo *) &MBX[MBXout * ESC_MBXSIZE]; + offset = ESCvar.fragsleft; + size = ESCvar.frags - ESCvar.fragsleft; + uint8_t command = COE_COMMAND_UPLOADSEGMENT + + (coesdo->command & COE_TOGGLEBIT); /* copy toggle bit */ + init_coesdo(coeres, COE_SDORESPONSE, command, + coesdo->index, coesdo->subindex); + if ((size + COE_SEGMENTHEADERSIZE) > ESC_MBXDSIZE) + { + /* more segmented transfer needed */ + /* limit to mailbox size */ + size = ESC_MBXDSIZE - COE_SEGMENTHEADERSIZE; + /* number of bytes done */ + ESCvar.fragsleft += size; + coeres->mbxheader.length = htoes (COE_SEGMENTHEADERSIZE + size); + } + else + { + /* last segment */ + ESCvar.segmented = 0; + ESCvar.frags = 0; + ESCvar.fragsleft = 0; + coeres->command += COE_COMMAND_LASTSEGMENTBIT; + if (size >= 7) + { + coeres->mbxheader.length = htoes (COE_SEGMENTHEADERSIZE + size); + } + else + { + coeres->command += (7 - size) << 1; + coeres->mbxheader.length = htoes (COE_DEFAULTLENGTH); + } + } + copy2mbx ((uint8_t *) ESCvar.data + offset, (&(coeres->command)) + 1, + size); /* copy to mailbox */ + + if (ESCvar.segmented == 0) + { + abort = ESC_upload_post_objecthandler (etohs (coesdo->index), + coesdo->subindex, ESCvar.flags); + if (abort != 0) + { + SDO_abort (etohs (coesdo->index), coesdo->subindex, abort); + } + } + + MBXcontrol[MBXout].state = MBXstate_outreq; + } + MBXcontrol[0].state = MBXstate_idle; + ESCvar.xoe = 0; +} + + +/** Function for handling incoming requested SDO Download, validating the + * request and sending an response. On error an SDO Abort will be sent. + */ +static void SDO_download (void) +{ + _COEsdo *coesdo, *coeres; + uint16_t index; + uint8_t subindex; + int16_t nidx, nsub; + uint8_t MBXout; + uint16_t size, actsize; + const _objd *objd; + uint32_t *mbxdata; + uint32_t abort; + + coesdo = (_COEsdo *) &MBX[0]; + index = etohs (coesdo->index); + subindex = coesdo->subindex; + nidx = SDO_findobject (index); + if (nidx >= 0) + { + nsub = SDO_findsubindex (nidx, subindex); + if (nsub >= 0) + { + objd = SDOobjects[nidx].objdesc; + uint8_t access = (objd + nsub)->flags & 0x3f; + uint8_t state = ESCvar.ALstatus & 0x0f; + if (WRITE_ACCESS(access, state)) + { + /* expedited? */ + if (coesdo->command & COE_EXPEDITED_INDICATOR) + { + size = 4 - ((coesdo->command & 0x0c) >> 2); + mbxdata = &(coesdo->size); + } + else + { + /* normal download */ + size = (etohl (coesdo->size) & 0xffff); + mbxdata = (&(coesdo->size)) + 1; + } + actsize = BITS2BYTES((objd + nsub)->bitlength); + if (actsize != size) + { + /* entries with data types VISIBLE_STRING, OCTET_STRING, + * UNICODE_STRING, ARRAY_OF_INT, ARRAY_OF_SINT, + * ARRAY_OF_DINT, and ARRAY_OF_UDINT may have flexible length + */ + uint16_t type = (objd + nsub)->datatype; + if (type == DTYPE_VISIBLE_STRING) + { + /* pad with zeroes up to the maximum size of the entry */ + memset((objd + nsub)->data + size, 0, actsize - size); + } + else if ((type != DTYPE_OCTET_STRING) && + (type != DTYPE_UNICODE_STRING) && + (type != DTYPE_ARRAY_OF_INT) && + (type != DTYPE_ARRAY_OF_SINT) && + (type != DTYPE_ARRAY_OF_DINT) && + (type != DTYPE_ARRAY_OF_UDINT)) + { + set_state_idle (index, subindex, ABORT_TYPEMISMATCH); + return; + } + } + abort = ESC_download_pre_objecthandler ( + index, + subindex, + mbxdata, + size, + (objd + nsub)->flags + ); + if (abort == 0) + { + if ((size > 4) && + (size > (coesdo->mbxheader.length - COE_HEADERSIZE))) + { + size = coesdo->mbxheader.length - COE_HEADERSIZE; + /* signal segmented transfer */ + ESCvar.segmented = MBXSED; + ESCvar.data = (objd + nsub)->data + size; + ESCvar.index = index; + ESCvar.subindex = subindex; + ESCvar.flags = (objd + nsub)->flags; + } + else + { + ESCvar.segmented = 0; + } + copy2mbx (mbxdata, (objd + nsub)->data, size); + MBXout = ESC_claimbuffer (); + if (MBXout) + { + coeres = (_COEsdo *) &MBX[MBXout * ESC_MBXSIZE]; + coeres->mbxheader.length = htoes (COE_DEFAULTLENGTH); + coeres->mbxheader.mbxtype = MBXCOE; + coeres->coeheader.numberservice = + htoes ((0 & 0x01f) | (COE_SDORESPONSE << 12)); + coeres->index = htoes (index); + coeres->subindex = subindex; + coeres->command = COE_COMMAND_DOWNLOADRESPONSE; + coeres->size = htoel (0); + MBXcontrol[MBXout].state = MBXstate_outreq; + } + if (ESCvar.segmented == 0) + { + /* external object write handler */ + abort = ESC_download_post_objecthandler (index, subindex, (objd + nsub)->flags); + if (abort != 0) + { + SDO_abort (index, subindex, abort); + } + } + } + else + { + SDO_abort (index, subindex, abort); + } + } + else + { + if (access == ATYPE_RWpre) + { + SDO_abort (index, subindex, ABORT_NOTINTHISSTATE); + } + else + { + SDO_abort (index, subindex, ABORT_READONLY); + } + } + } + else + { + SDO_abort (index, subindex, ABORT_NOSUBINDEX); + } + } + else + { + SDO_abort (index, subindex, ABORT_NOOBJECT); + } + MBXcontrol[0].state = MBXstate_idle; + ESCvar.xoe = 0; +} + +/** Function for handling incoming requested SDO Download with Complete Access, + * validating the request and sending a response. On error an SDO Abort will + * be sent. + */ +static void SDO_download_complete_access (void) +{ + _COEsdo *coesdo = (_COEsdo *) &MBX[0]; + uint16_t index; + uint8_t subindex; + int16_t nidx, nsub; + uint32_t abortcode = complete_access_get_variables + (coesdo, &index, &subindex, &nidx, &nsub); + if (abortcode != 0) + { + set_state_idle (index, subindex, abortcode); + return; + } + + uint16_t bytes; + uint32_t *mbxdata = &(coesdo->size); + + if (coesdo->command & COE_EXPEDITED_INDICATOR) + { + /* expedited download */ + bytes = 4 - ((coesdo->command & 0x0c) >> 2); + } + else + { + /* normal download */ + bytes = (etohl (coesdo->size) & 0xffff); + mbxdata++; + } + + const _objd *objd = SDOobjects[nidx].objdesc; + + /* loop through the subindexes to get the total size */ + uint32_t size = complete_access_subindex_loop(objd, nidx, nsub, NULL, DOWNLOAD, 0); + if (size > 0xffff) + { + /* 'size' is in this case actually an abort code */ + set_state_idle (index, subindex, size); + return; + } + /* The document ETG.1020 S (R) V1.3.0, chapter 12.2, states that + * "The SDO Download Complete Access data length shall always match + * the full current object size (defined by SubIndex0)". + * But EtherCAT Conformance Test Tool doesn't follow this rule for some test + * cases, which is the reason to here only check for 'less than or equal'. + */ + else if (bytes <= BITS2BYTES(size)) + { + abortcode = ESC_download_pre_objecthandler(index, subindex, mbxdata, + size, objd->flags | COMPLETE_ACCESS_FLAG); + if (abortcode != 0) + { + set_state_idle (index, subindex, abortcode); + return; + } + + /* copy download data to subindexes */ + complete_access_subindex_loop(objd, nidx, nsub, (uint8_t *)mbxdata, DOWNLOAD, bytes); + + abortcode = ESC_download_post_objecthandler(index, subindex, + objd->flags | COMPLETE_ACCESS_FLAG); + if (abortcode != 0) + { + set_state_idle (index, subindex, abortcode); + return; + } + } + else + { + set_state_idle (index, subindex, ABORT_TYPEMISMATCH); + return; + } + + uint8_t MBXout = ESC_claimbuffer (); + if (MBXout > 0) + { + _COEsdo *coeres = (_COEsdo *) &MBX[MBXout * ESC_MBXSIZE]; + init_coesdo(coeres, COE_SDORESPONSE, + COE_COMMAND_DOWNLOADRESPONSE | COE_COMPLETEACCESS, + index, subindex); + + coeres->size = 0; + MBXcontrol[MBXout].state = MBXstate_outreq; + } + + set_state_idle (index, subindex, 0); +} + +static void SDO_downloadsegment (void) +{ + _COEsdo *coesdo = (_COEsdo *) &MBX[0]; + uint8_t MBXout = ESC_claimbuffer (); + if (MBXout) + { + _COEsdo *coeres = (_COEsdo *) &MBX[MBXout * ESC_MBXSIZE]; + uint32_t size = coesdo->mbxheader.length - 3; + if (size == 7) + { + size = 7 - ((coesdo->command >> 1) & 7); + } + uint8_t command = COE_COMMAND_DOWNLOADSEGRESP + + (coesdo->command & COE_TOGGLEBIT); /* copy toggle bit */ + init_coesdo(coeres, COE_SDORESPONSE, command, 0, 0); + + uint32_t *mbxdata = (uint32_t *)&(coesdo->index); /* data pointer */ + copy2mbx (mbxdata, (uint8_t *)ESCvar.data, size); + + if (coesdo->command & COE_COMMAND_LASTSEGMENTBIT) + { + /* last segment */ + ESCvar.segmented = 0; + + /* external object write handler */ + uint32_t abort = ESC_download_post_objecthandler + (ESCvar.index, ESCvar.subindex, ESCvar.flags); + if (abort != 0) + { + set_state_idle (ESCvar.index, ESCvar.subindex, abort); + return; + } + } + else + { + /* more segmented transfer needed: increase offset */ + ESCvar.data += size; + } + + MBXcontrol[MBXout].state = MBXstate_outreq; + } + + set_state_idle (0, 0, 0); +} + +/** Function for sending an SDO Info Error reply. + * + * @param[in] abortcode = = abort code to send in reply + */ +static void SDO_infoerror (uint32_t abortcode) +{ + uint8_t MBXout; + _COEobjdesc *coeres; + MBXout = ESC_claimbuffer (); + if (MBXout) + { + coeres = (_COEobjdesc *) &MBX[MBXout * ESC_MBXSIZE]; + coeres->mbxheader.length = htoes ((uint16_t) COE_HEADERSIZE); + coeres->mbxheader.mbxtype = MBXCOE; + coeres->coeheader.numberservice = + htoes ((0 & 0x01f) | (COE_SDOINFORMATION << 12)); + /* SDO info error request */ + coeres->infoheader.opcode = COE_INFOERROR; + coeres->infoheader.incomplete = 0; + coeres->infoheader.reserved = 0x00; + coeres->infoheader.fragmentsleft = 0; + coeres->index = (uint16_t)htoel (abortcode); + coeres->datatype = (uint16_t)(htoel (abortcode) >> 16); + MBXcontrol[MBXout].state = MBXstate_outreq; + MBXcontrol[0].state = MBXstate_idle; + ESCvar.xoe = 0; + } +} + +#define ODLISTSIZE ((ESC_MBX1_sml - ESC_MBXHSIZE - sizeof(_COEh) - sizeof(_INFOh) - 2) & 0xfffe) + +/** Function for handling incoming requested SDO Get OD List, validating the + * request and sending an response. On error an SDO Info Error will be sent. + */ +static void SDO_getodlist (void) +{ + uint16_t frags; + uint8_t MBXout = 0; + uint16_t entries = 0; + uint16_t i, n; + uint16_t *p; + _COEobjdesc *coel, *coer; + + while (SDOobjects[entries].index != 0xffff) + { + entries++; + } + ESCvar.entries = entries; + frags = ((entries << 1) + ODLISTSIZE - 1); + frags /= ODLISTSIZE; + coer = (_COEobjdesc *) &MBX[0]; + /* check for unsupported opcodes */ + if (etohs (coer->index) > 0x01) + { + SDO_infoerror (ABORT_UNSUPPORTED); + } + else + { + MBXout = ESC_claimbuffer (); + } + if (MBXout) + { + coel = (_COEobjdesc *) &MBX[MBXout * ESC_MBXSIZE]; + coel->mbxheader.mbxtype = MBXCOE; + coel->coeheader.numberservice = + htoes ((0 & 0x01f) | (COE_SDOINFORMATION << 12)); + coel->infoheader.opcode = COE_GETODLISTRESPONSE; + /* number of objects request */ + if (etohs (coer->index) == 0x00) + { + coel->index = htoes ((uint16_t) 0x00); + coel->infoheader.incomplete = 0; + coel->infoheader.reserved = 0x00; + coel->infoheader.fragmentsleft = htoes ((uint16_t) 0); + MBXcontrol[0].state = MBXstate_idle; + ESCvar.xoe = 0; + ESCvar.frags = frags; + ESCvar.fragsleft = frags - 1; + p = &(coel->datatype); + *p = htoes (entries); + p++; + *p = 0; + p++; + *p = 0; + p++; + *p = 0; + p++; + *p = 0; + coel->mbxheader.length = htoes (0x08 + (5 << 1)); + } + /* only return all objects */ + if (etohs (coer->index) == 0x01) + { + if (frags > 1) + { + coel->infoheader.incomplete = 1; + ESCvar.xoe = MBXCOE + MBXODL; + n = ODLISTSIZE >> 1; + } + else + { + coel->infoheader.incomplete = 0; + MBXcontrol[0].state = MBXstate_idle; + ESCvar.xoe = 0; + n = entries; + } + coel->infoheader.reserved = 0x00; + ESCvar.frags = frags; + ESCvar.fragsleft = frags - 1; + coel->infoheader.fragmentsleft = htoes (ESCvar.fragsleft); + coel->index = htoes ((uint16_t) 0x01); + + p = &(coel->datatype); + for (i = 0; i < n; i++) + { + *p = htoes (SDOobjects[i].index); + p++; + } + + coel->mbxheader.length = htoes (0x08 + (n << 1)); + } + MBXcontrol[MBXout].state = MBXstate_outreq; + } +} +/** Function for continuing sending left overs from previous requested + * SDO Get OD List, validating the request and sending an response. + */ +static void SDO_getodlistcont (void) +{ + uint8_t MBXout; + uint16_t i, n, s; + uint16_t *p; + _COEobjdesc *coel; + + MBXout = ESC_claimbuffer (); + if (MBXout) + { + coel = (_COEobjdesc *) &MBX[MBXout * ESC_MBXSIZE]; + coel->mbxheader.mbxtype = MBXCOE; + coel->coeheader.numberservice = + htoes ((0 & 0x01f) | (COE_SDOINFORMATION << 12)); + coel->infoheader.opcode = COE_GETODLISTRESPONSE; + s = (ESCvar.frags - ESCvar.fragsleft) * (ODLISTSIZE >> 1); + if (ESCvar.fragsleft > 1) + { + coel->infoheader.incomplete = 1; + n = s + (ODLISTSIZE >> 1); + } + else + { + coel->infoheader.incomplete = 0; + MBXcontrol[0].state = MBXstate_idle; + ESCvar.xoe = 0; + n = ESCvar.entries; + } + coel->infoheader.reserved = 0x00; + ESCvar.fragsleft--; + coel->infoheader.fragmentsleft = htoes (ESCvar.fragsleft); + /* pointer 2 bytes back to exclude index */ + p = &(coel->index); + for (i = s; i < n; i++) + { + *p = htoes (SDOobjects[i].index); + p++; + } + coel->mbxheader.length = htoes (0x06 + ((n - s) << 1)); + MBXcontrol[MBXout].state = MBXstate_outreq; + } +} + +/** Function for handling incoming requested SDO Get Object Description, + * validating the request and sending an response. On error an + * SDO Info Error will be sent. + */ +static void SDO_getod (void) +{ + uint8_t MBXout; + uint16_t index; + int32_t nidx; + uint8_t *d; + const uint8_t *s; + uint8_t n = 0; + _COEobjdesc *coer, *coel; + coer = (_COEobjdesc *) &MBX[0]; + index = etohs (coer->index); + nidx = SDO_findobject (index); + if (nidx >= 0) + { + MBXout = ESC_claimbuffer (); + if (MBXout) + { + coel = (_COEobjdesc *) &MBX[MBXout * ESC_MBXSIZE]; + coel->mbxheader.mbxtype = MBXCOE; + coel->coeheader.numberservice = + htoes ((0 & 0x01f) | (COE_SDOINFORMATION << 12)); + coel->infoheader.opcode = COE_GETODRESPONSE; + coel->infoheader.incomplete = 0; + coel->infoheader.reserved = 0x00; + coel->infoheader.fragmentsleft = htoes (0); + coel->index = htoes (index); + if (SDOobjects[nidx].objtype == OTYPE_VAR) + { + int32_t nsub = SDO_findsubindex (nidx, 0); + const _objd *objd = SDOobjects[nidx].objdesc; + coel->datatype = htoes ((objd + nsub)->datatype); + coel->maxsub = SDOobjects[nidx].maxsub; + } + else if (SDOobjects[nidx].objtype == OTYPE_ARRAY) + { + int32_t nsub = SDO_findsubindex (nidx, 0); + const _objd *objd = SDOobjects[nidx].objdesc; + coel->datatype = htoes ((objd + nsub)->datatype); + coel->maxsub = SDOobjects[nidx].objdesc->value; + } + else + { + coel->datatype = htoes (0); + coel->maxsub = SDOobjects[nidx].objdesc->value; + } + coel->objectcode = SDOobjects[nidx].objtype; + s = (uint8_t *) SDOobjects[nidx].name; + d = (uint8_t *) &(coel->name); + while (*s && (n < (ESC_MBXDSIZE - 0x0c))) + { + *d = *s; + n++; + s++; + d++; + } + *d = *s; + coel->mbxheader.length = htoes ((uint16_t) 0x0c + n); + MBXcontrol[MBXout].state = MBXstate_outreq; + MBXcontrol[0].state = MBXstate_idle; + ESCvar.xoe = 0; + } + } + else + { + SDO_infoerror (ABORT_NOOBJECT); + } +} + +/** Function for handling incoming requested SDO Get Entry Description, + * validating the request and sending an response. On error an + * SDO Info Error will be sent. + */ +static void SDO_geted (void) +{ + uint8_t MBXout; + uint16_t index; + int32_t nidx, nsub; + uint8_t subindex; + uint8_t *d; + const uint8_t *s; + const _objd *objd; + uint8_t n = 0; + _COEentdesc *coer, *coel; + coer = (_COEentdesc *) &MBX[0]; + index = etohs (coer->index); + subindex = coer->subindex; + nidx = SDO_findobject (index); + if (nidx >= 0) + { + nsub = SDO_findsubindex (nidx, subindex); + if (nsub >= 0) + { + objd = SDOobjects[nidx].objdesc; + MBXout = ESC_claimbuffer (); + if (MBXout) + { + coel = (_COEentdesc *) &MBX[MBXout * ESC_MBXSIZE]; + coel->mbxheader.mbxtype = MBXCOE; + coel->coeheader.numberservice = + htoes ((0 & 0x01f) | (COE_SDOINFORMATION << 12)); + coel->infoheader.opcode = COE_ENTRYDESCRIPTIONRESPONSE; + coel->infoheader.incomplete = 0; + coel->infoheader.reserved = 0x00; + coel->infoheader.fragmentsleft = htoes ((uint16_t) 0); + coel->index = htoes (index); + coel->subindex = subindex; + coel->valueinfo = COE_VALUEINFO_ACCESS + + COE_VALUEINFO_OBJECT + COE_VALUEINFO_MAPPABLE; + coel->datatype = htoes ((objd + nsub)->datatype); + coel->bitlength = htoes ((objd + nsub)->bitlength); + coel->access = htoes ((objd + nsub)->flags); + s = (uint8_t *) (objd + nsub)->name; + d = (uint8_t *) &(coel->name); + while (*s && (n < (ESC_MBXDSIZE - 0x10))) + { + *d = *s; + n++; + s++; + d++; + } + *d = *s; + coel->mbxheader.length = htoes ((uint16_t) 0x10 + n); + MBXcontrol[MBXout].state = MBXstate_outreq; + MBXcontrol[0].state = MBXstate_idle; + ESCvar.xoe = 0; + } + } + else + { + SDO_infoerror (ABORT_NOSUBINDEX); + } + } + else + { + SDO_infoerror (ABORT_NOOBJECT); + } +} + +/** Main CoE function checking the status on current mailbox buffers carrying + * data, distributing the mailboxes to appropriate CoE functions. + * On Error an MBX_error or SDO Abort will be sent depending on error cause. + */ +void ESC_coeprocess (void) +{ + _MBXh *mbh; + _COEsdo *coesdo; + _COEobjdesc *coeobjdesc; + uint8_t service; + if (ESCvar.MBXrun == 0) + { + return; + } + if (!ESCvar.xoe && (MBXcontrol[0].state == MBXstate_inclaim)) + { + mbh = (_MBXh *) &MBX[0]; + if (mbh->mbxtype == MBXCOE) + { + if (etohs (mbh->length) < COE_MINIMUM_LENGTH) + { + MBX_error (MBXERR_INVALIDSIZE); + } + else + { + ESCvar.xoe = MBXCOE; + } + } + } + if ((ESCvar.xoe == (MBXCOE + MBXODL)) && (!ESCvar.mbxoutpost)) + { + /* continue get OD list */ + SDO_getodlistcont (); + } + if (ESCvar.xoe == MBXCOE) + { + coesdo = (_COEsdo *) &MBX[0]; + coeobjdesc = (_COEobjdesc *) &MBX[0]; + service = etohs (coesdo->coeheader.numberservice) >> 12; + if (service == COE_SDOREQUEST) + { + if ((SDO_COMMAND(coesdo->command) == COE_COMMAND_UPLOADREQUEST) + && (etohs (coesdo->mbxheader.length) == COE_HEADERSIZE)) + { + /* initiate SDO upload request */ + if (SDO_COMPLETE_ACCESS(coesdo->command)) + { + SDO_upload_complete_access (); + } + else + { + SDO_upload (); + } + } + else if (((coesdo->command & 0xef) == COE_COMMAND_UPLOADSEGREQ) + && (etohs (coesdo->mbxheader.length) == COE_HEADERSIZE) + && (ESCvar.segmented == MBXSEU)) + { + /* SDO upload segment request */ + SDO_uploadsegment (); + } + else if (SDO_COMMAND(coesdo->command) == COE_COMMAND_DOWNLOADREQUEST) + { + /* initiate SDO download request */ + if (SDO_COMPLETE_ACCESS(coesdo->command)) + { + SDO_download_complete_access (); + } + else + { + SDO_download (); + } + } + else if (SDO_COMMAND(coesdo->command) == COE_COMMAND_DOWNLOADSEGREQ) + { + /* SDO download segment request */ + SDO_downloadsegment (); + } + } + /* initiate SDO get OD list */ + else + { + if ((service == COE_SDOINFORMATION) + && (coeobjdesc->infoheader.opcode == 0x01)) + { + SDO_getodlist (); + } + /* initiate SDO get OD */ + else + { + if ((service == COE_SDOINFORMATION) + && (coeobjdesc->infoheader.opcode == 0x03)) + { + SDO_getod (); + } + /* initiate SDO get ED */ + else + { + if ((service == COE_SDOINFORMATION) + && (coeobjdesc->infoheader.opcode == 0x05)) + { + SDO_geted (); + } + else + { + /* COE not recognised above */ + if (ESCvar.xoe == MBXCOE) + { + if (service == 0) + { + MBX_error (MBXERR_INVALIDHEADER); + } + else + { + SDO_abort (etohs (coesdo->index), coesdo->subindex, ABORT_UNSUPPORTED); + } + MBXcontrol[0].state = MBXstate_idle; + ESCvar.xoe = 0; + } + } + } + } + } + } +} + +/** + * Get value from bitmap + * + * This function gets a value from a bitmap. + * + * @param[in] bitmap = bitmap containing value + * @param[in] offset = start offset + * @param[in] length = number of bits to get + * @return bitslice value + */ +static uint64_t COE_bitsliceGet (uint64_t * bitmap, int offset, int length) +{ + const int word_offset = offset / 64; + const int bit_offset = offset % 64; + const uint64_t mask = (length == 64) ? UINT64_MAX : (1ULL << length) - 1; + uint64_t w0; + uint64_t w1 = 0; + + /* Get the least significant word */ + w0 = bitmap[word_offset]; + w0 = w0 >> bit_offset; + + /* Get the most significant word, if required */ + if (length + bit_offset > 64) + { + w1 = bitmap[word_offset + 1]; + w1 = w1 << (64 - bit_offset); + } + + w0 = (w1 | w0); + return (w0 & mask); +} + +/** + * Set value in bitmap + * + * This function sets a value in a bitmap. + * + * @param[in] bitmap = bitmap to contain value + * @param[in] offset = start offset + * @param[in] length = number of bits to set + * @param[in] value = value to set + */ +static void COE_bitsliceSet (uint64_t * bitmap, int offset, int length, + uint64_t value) +{ + const int word_offset = offset / 64; + const int bit_offset = offset % 64; + const uint64_t mask = (length == 64) ? UINT64_MAX : (1ULL << length) - 1; + const uint64_t mask0 = mask << bit_offset; + uint64_t v0 = value << bit_offset; + uint64_t w0 = bitmap[word_offset]; + + /* Set the least significant word */ + w0 = (w0 & ~mask0) | (v0 & mask0); + bitmap[word_offset] = w0; + + /* Set the most significant word, if required */ + if (length + bit_offset > 64) + { + const uint64_t mask1 = mask >> (64 - bit_offset); + uint64_t v1 = value >> (64 - bit_offset); + uint64_t w1 = bitmap[word_offset + 1]; + + w1 = (w1 & ~mask1) | (v1 & mask1); + bitmap[word_offset + 1] = w1; + } +} + +/** + * Get object value + * + * This function atomically gets an object value. + * + * @param[in] obj = object description + * @return object value + */ +static uint64_t COE_getValue (const _objd * obj) +{ + uint64_t value = 0; + + /* TODO: const data */ + + switch(obj->datatype) + { + case DTYPE_BIT1: + case DTYPE_BIT2: + case DTYPE_BIT3: + case DTYPE_BIT4: + case DTYPE_BIT5: + case DTYPE_BIT6: + case DTYPE_BIT7: + case DTYPE_BIT8: + case DTYPE_BOOLEAN: + case DTYPE_UNSIGNED8: + case DTYPE_INTEGER8: + case DTYPE_BITARR8: + value = *(uint8_t *)obj->data; + break; + + case DTYPE_UNSIGNED16: + case DTYPE_INTEGER16: + case DTYPE_BITARR16: + value = *(uint16_t *)obj->data; + break; + + case DTYPE_REAL32: + case DTYPE_UNSIGNED32: + case DTYPE_INTEGER32: + case DTYPE_BITARR32: + value = *(uint32_t *)obj->data; + break; + + case DTYPE_REAL64: + case DTYPE_UNSIGNED64: + case DTYPE_INTEGER64: + /* FIXME: must be atomic */ + value = *(uint64_t *)obj->data; + break; + + default: + CC_ASSERT (0); + } + + return value; +} + +/** + * Set object value + * + * This function atomically sets an object value. + * + * @param[in] obj = object description + * @param[in] value = new value + */ +static void COE_setValue (const _objd * obj, uint64_t value) +{ + switch(obj->datatype) + { + case DTYPE_BIT1: + case DTYPE_BIT2: + case DTYPE_BIT3: + case DTYPE_BIT4: + case DTYPE_BIT5: + case DTYPE_BIT6: + case DTYPE_BIT7: + case DTYPE_BIT8: + case DTYPE_BOOLEAN: + case DTYPE_UNSIGNED8: + case DTYPE_INTEGER8: + case DTYPE_BITARR8: + *(uint8_t *)obj->data = value & UINT8_MAX; + break; + + case DTYPE_UNSIGNED16: + case DTYPE_INTEGER16: + case DTYPE_BITARR16: + *(uint16_t *)obj->data = value & UINT16_MAX; + break; + + case DTYPE_REAL32: + case DTYPE_UNSIGNED32: + case DTYPE_INTEGER32: + case DTYPE_BITARR32: + *(uint32_t *)obj->data = value & UINT32_MAX; + break; + + case DTYPE_REAL64: + case DTYPE_UNSIGNED64: + case DTYPE_INTEGER64: + /* FIXME: must be atomic */ + *(uint64_t *)obj->data = value; + break; + + default: + DPRINT ("ignored\n"); + break; + } +} + +/** + * Init default values for SDO objects + */ +void COE_initDefaultValues (void) +{ + int i; + const _objd *objd; + int n; + uint8_t maxsub; + + /* Let application decide if initialization will be skipped */ + if (ESCvar.skip_default_initialization) + { + return; + } + + /* Set default values from object descriptor */ + for (n = 0; SDOobjects[n].index != 0xffff; n++) + { + objd = SDOobjects[n].objdesc; + maxsub = SDOobjects[n].maxsub; + + i = 0; + do + { + if (objd[i].data != NULL) + { + COE_setValue (&objd[i], objd[i].value); + DPRINT ("%04x:%02x = %x\n", SDOobjects[n].index, objd[i].subindex, objd[i].value); + } + } while (objd[i++].subindex < maxsub); + } + + /* Let application override default values */ + if (ESCvar.set_defaults_hook != NULL) + { + ESCvar.set_defaults_hook(); + } +} + +/** + * Pack process data + * + * This function reads mapped objects and constructs the process data + * inputs (TXPDO). + * + * @param[in] buffer = input process data + * @param[in] nmappings = number of mappings in sync manager + * @param[in] mappings = list of mapped objects in sync manager + */ +void COE_pdoPack (uint8_t * buffer, int nmappings, _SMmap * mappings) +{ + int ix; + + /* Check that buffer is aligned on 64-bit boundary */ + CC_ASSERT (((uintptr_t)buffer & 0x07) == 0); + + for (ix = 0; ix < nmappings; ix++) + { + const _objd * obj = mappings[ix].obj; + uint16_t offset = mappings[ix].offset; + + if (obj != NULL) + { + if (obj->bitlength > 64) + { + memcpy ( + &buffer[BITS2BYTES (offset)], + obj->data, + BITS2BYTES (obj->bitlength) + ); + } + else + { + /* Atomically get object value */ + uint64_t value = COE_getValue (obj); + COE_bitsliceSet ( + (uint64_t *)buffer, + offset, + obj->bitlength, + value + ); + } + } + } +} + +/** + * Unpack process data + * + * This function unpacks process data output (RXPDO) and writes to the + * mapped objects. + * + * @param[in] buffer = output process data + * @param[in] nmappings = number of mappings in sync manager + * @param[in] mappings = list of mapped objects in sync manager + */ +void COE_pdoUnpack (uint8_t * buffer, int nmappings, _SMmap * mappings) +{ + int ix; + + /* Check that buffer is aligned on 64-bit boundary */ + CC_ASSERT (((uintptr_t)buffer & 0x07) == 0); + + for (ix = 0; ix < nmappings; ix++) + { + const _objd * obj = mappings[ix].obj; + uint16_t offset = mappings[ix].offset; + + if (obj != NULL) + { + if (obj->bitlength > 64) + { + memcpy ( + obj->data, + &buffer[BITS2BYTES (offset)], + BITS2BYTES (obj->bitlength) + ); + } + else + { + /* Atomically set object value */ + uint64_t value = COE_bitsliceGet ( + (uint64_t *)buffer, + offset, + obj->bitlength + ); + COE_setValue (obj, value); + } + } + } +} + +/** + * Fetch max subindex + * + * This function fetches the value of subindex 0 (max subindex). + * + * @param[in] index = object index + */ +uint8_t COE_maxSub (uint16_t index) +{ + int nidx; + uint8_t maxsub; + + nidx = SDO_findobject (index); + if (nidx == -1) + return 0; + + maxsub = OBJ_VALUE_FETCH (maxsub, SDOobjects[nidx].objdesc[0]); + return maxsub; +} diff --git a/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/esc_coe.h b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/esc_coe.h new file mode 100755 index 0000000..689750c --- /dev/null +++ b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/esc_coe.h @@ -0,0 +1,135 @@ +/* + * Licensed under the GNU General Public License version 2 with exceptions. See + * LICENSE file in the project root for full license information + */ + + /** \file + * \brief + * Headerfile for esc_coe.c + */ + +#ifndef __esc_coe__ +#define __esc_coe__ + +#include + +CC_PACKED_BEGIN +typedef struct CC_PACKED +{ + uint16_t subindex; + uint16_t datatype; + uint16_t bitlength; + uint16_t flags; + const char *name; + uint32_t value; + void *data; +} _objd; +CC_PACKED_END + +CC_PACKED_BEGIN +typedef struct CC_PACKED +{ + uint16_t index; + uint16_t objtype; + uint8_t maxsub; + uint8_t pad1; + const char *name; + const _objd *objdesc; +} _objectlist; +CC_PACKED_END + +typedef struct +{ + const _objd * obj; + uint16_t offset; +} _SMmap; + +#define OBJH_READ 0 +#define OBJH_WRITE 1 + +#define OTYPE_DOMAIN 0x0002 +#define OTYPE_DEFTYPE 0x0005 +#define OTYPE_DEFSTRUCT 0x0006 +#define OTYPE_VAR 0x0007 +#define OTYPE_ARRAY 0x0008 +#define OTYPE_RECORD 0x0009 + +#define DTYPE_BOOLEAN 0x0001 +#define DTYPE_INTEGER8 0x0002 +#define DTYPE_INTEGER16 0x0003 +#define DTYPE_INTEGER32 0x0004 +#define DTYPE_UNSIGNED8 0x0005 +#define DTYPE_UNSIGNED16 0x0006 +#define DTYPE_UNSIGNED32 0x0007 +#define DTYPE_REAL32 0x0008 +#define DTYPE_VISIBLE_STRING 0x0009 +#define DTYPE_OCTET_STRING 0x000A +#define DTYPE_UNICODE_STRING 0x000B +#define DTYPE_INTEGER24 0x0010 +#define DTYPE_UNSIGNED24 0x0016 +#define DTYPE_INTEGER64 0x0015 +#define DTYPE_UNSIGNED64 0x001B +#define DTYPE_REAL64 0x0011 +#define DTYPE_PDO_MAPPING 0x0021 +#define DTYPE_IDENTITY 0x0023 +#define DTYPE_BITARR8 0x002D +#define DTYPE_BITARR16 0x002E +#define DTYPE_BITARR32 0x002F +#define DTYPE_BIT1 0x0030 +#define DTYPE_BIT2 0x0031 +#define DTYPE_BIT3 0x0032 +#define DTYPE_BIT4 0x0033 +#define DTYPE_BIT5 0x0034 +#define DTYPE_BIT6 0x0035 +#define DTYPE_BIT7 0x0036 +#define DTYPE_BIT8 0x0037 +#define DTYPE_ARRAY_OF_INT 0x0260 +#define DTYPE_ARRAY_OF_SINT 0x0261 +#define DTYPE_ARRAY_OF_DINT 0x0262 +#define DTYPE_ARRAY_OF_UDINT 0x0263 + +#define ATYPE_Rpre 0x01 +#define ATYPE_Rsafe 0x02 +#define ATYPE_Rop 0x04 +#define ATYPE_Wpre 0x08 +#define ATYPE_Wsafe 0x10 +#define ATYPE_Wop 0x20 +#define ATYPE_RXPDO 0x40 +#define ATYPE_TXPDO 0x80 + +#define ATYPE_RO (ATYPE_Rpre | ATYPE_Rsafe | ATYPE_Rop) +#define ATYPE_WO (ATYPE_Wpre | ATYPE_Wsafe | ATYPE_Wop) +#define ATYPE_RW (ATYPE_RO | ATYPE_WO) +#define ATYPE_RWpre (ATYPE_Wpre | ATYPE_RO) + +#define TX_PDO_OBJIDX 0x1c13 +#define RX_PDO_OBJIDX 0x1c12 + +#define COMPLETE_ACCESS_FLAG (1 << 15) + +void ESC_coeprocess (void); +int16_t SDO_findsubindex (int16_t nidx, uint8_t subindex); +int32_t SDO_findobject (uint16_t index); +uint16_t sizeOfPDO (uint16_t index, int * nmappings, _SMmap * sm, int max_mappings); +void SDO_abort (uint16_t index, uint8_t subindex, uint32_t abortcode); +void COE_initDefaultValues (void); + +void COE_pdoPack (uint8_t * buffer, int nmappings, _SMmap * sm); +void COE_pdoUnpack (uint8_t * buffer, int nmappings, _SMmap * sm); +uint8_t COE_maxSub (uint16_t index); + +extern uint32_t ESC_download_post_objecthandler (uint16_t index, uint8_t subindex, uint16_t flags); +extern uint32_t ESC_download_pre_objecthandler (uint16_t index, + uint8_t subindex, + void * data, + size_t size, + uint16_t flags); +extern uint32_t ESC_upload_pre_objecthandler (uint16_t index, + uint8_t subindex, + void * data, + size_t size, + uint16_t flags); +extern uint32_t ESC_upload_post_objecthandler (uint16_t index, uint8_t subindex, uint16_t flags); +extern const _objectlist SDOobjects[]; + +#endif diff --git a/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/esc_eep.c b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/esc_eep.c new file mode 100755 index 0000000..8736ac0 --- /dev/null +++ b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/esc_eep.c @@ -0,0 +1,80 @@ +/* + * Licensed under the GNU General Public License version 2 with exceptions. See + * LICENSE file in the project root for full license information + */ + + /** \file + * \brief + * ESI EEPROM emulator module. + */ + +#include "cc.h" +#include "esc.h" +#include "esc_eep.h" + +#include + +static uint8_t eep_buf[8]; + +/** EPP periodic task of ESC side EEPROM emulation. + * + */ +void EEP_process (void) +{ + eep_stat_t stat; + + /* check for eeprom event */ + if ((ESCvar.ALevent & ESCREG_ALEVENT_EEP) == 0) { + return; + } + + while (1) { + /* read eeprom status */ + ESC_read (ESCREG_EECONTSTAT, &stat, sizeof (eep_stat_t)); + stat.contstat.reg = etohs(stat.contstat.reg); + stat.addr = etohl(stat.addr); + + /* check busy flag, exit if job finished */ + if (!stat.contstat.bits.busy) { + return; + } + + /* clear error bits */ + stat.contstat.bits.csumErr = 0; + stat.contstat.bits.eeLoading = 0; + stat.contstat.bits.ackErr = 0; + stat.contstat.bits.wrErr = 0; + + /* process commands */ + switch (stat.contstat.bits.cmdReg) { + case EEP_CMD_IDLE: + break; + + case EEP_CMD_READ: + case EEP_CMD_RELOAD: + /* handle read request */ + if (EEP_read (stat.addr * sizeof(uint16_t), eep_buf, EEP_READ_SIZE) != 0) { + stat.contstat.bits.ackErr = 1; + } else { + ESC_write (ESCREG_EEDATA, eep_buf, EEP_READ_SIZE); + } + break; + + case EEP_CMD_WRITE: + /* handle write request */ + ESC_read (ESCREG_EEDATA, eep_buf, EEP_WRITE_SIZE); + if (EEP_write (stat.addr * sizeof(uint16_t), eep_buf, EEP_WRITE_SIZE) != 0) { + stat.contstat.bits.ackErr = 1; + } + break; + + default: + stat.contstat.bits.ackErr = 1; + } + + /* acknowledge command */ + stat.contstat.reg = htoes(stat.contstat.reg); + ESC_write (ESCREG_EECONTSTAT, &stat.contstat.reg, sizeof(uint16_t)); + } +} + diff --git a/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/esc_eep.h b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/esc_eep.h new file mode 100755 index 0000000..b94f3d4 --- /dev/null +++ b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/esc_eep.h @@ -0,0 +1,77 @@ +/* + * Licensed under the GNU General Public License version 2 with exceptions. See + * LICENSE file in the project root for full license information + */ + +/** \file + * \brief + * Headerfile for esc_eep.c + */ + +#ifndef __esc_eep__ +#define __esc_eep__ + +#include +#include "esc.h" + +/* EEPROM commands */ +#define EEP_CMD_IDLE 0x0 +#define EEP_CMD_READ 0x1 +#define EEP_CMD_WRITE 0x2 +#define EEP_CMD_RELOAD 0x3 + +/* read/write size */ +#define EEP_READ_SIZE 8 +#define EEP_WRITE_SIZE 2 + +/* CONSTAT register content */ +typedef struct CC_PACKED +{ + union { + uint16_t reg; + struct { + uint8_t wrEnable:1; + uint8_t reserved:4; + uint8_t eeEmulated:1; + uint8_t eightByteRead:1; + uint8_t twoByteAddr:1; + + uint8_t cmdReg:3; + uint8_t csumErr:1; + uint8_t eeLoading:1; + uint8_t ackErr:1; + uint8_t wrErr:1; + uint8_t busy:1; + } bits; + } contstat; + + uint32_t addr; +} eep_stat_t; + +/** + * ECAT EEPROM configuration area data structure + */ +typedef union eep_config +{ + struct + { + uint16_t pdi_control; + uint16_t pdi_configuration; + uint16_t sync_impulse_len; + uint16_t pdi_configuration2; + uint16_t configured_station_alias; + uint8_t reserved[4]; + uint16_t checksum; + }; + uint32_t dword[4]; /**< Four 32 bit double word equivalent to 8 16 bit configuration area word. */ +}eep_config_t; + +/* periodic task */ +void EEP_process (void); + +/* From hardware file */ +void EEP_init (void); +int8_t EEP_read (uint32_t addr, uint8_t *data, uint16_t size); +int8_t EEP_write (uint32_t addr, uint8_t *data, uint16_t size); + +#endif diff --git a/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/esc_eoe.c b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/esc_eoe.c new file mode 100755 index 0000000..6332cf8 --- /dev/null +++ b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/esc_eoe.c @@ -0,0 +1,1066 @@ +/* + * Licensed under the GNU General Public License version 2 with exceptions. See + * LICENSE file in the project root for full license information + */ + + /** \file + * \brief + * Ethernet over EtherCAT (EoE) module. + */ + +#include +#include +#include "esc.h" +#include "esc_eoe.h" + + +#if defined(EC_BIG_ENDIAN) +#define EOE_HTONS(x) (x) +#define EOE_NTOHS(x) (x) +#define EOE_HTONL(x) (x) +#define EOE_NTOHL(x) (x) +#else +#define EOE_HTONS(x) ((((x) & 0x00ffUL) << 8) | (((x) & 0xff00UL) >> 8)) +#define EOE_NTOHS(x) EOE_HTONS(x) +#define EOE_HTONL(x) ((((x) & 0x000000ffUL) << 24) | \ + (((x) & 0x0000ff00UL) << 8) | \ + (((x) & 0x00ff0000UL) >> 8) | \ + (((x) & 0xff000000UL) >> 24)) +#define EOE_NTOHL(x) EOE_HTONL(x) +#endif /* #if defined(EC_BIG_ENDIAN) */ + +#define EOE_MAKEU32(a,b,c,d) (((uint32_t)((a) & 0xff) << 24) | \ + ((uint32_t)((b) & 0xff) << 16) | \ + ((uint32_t)((c) & 0xff) << 8) | \ + (uint32_t)((d) & 0xff)) + +/** Get one byte from the 4-byte address */ +#define eoe_ip4_addr1(ipaddr) (((const uint8_t*)(&(ipaddr)->addr))[0]) +#define eoe_ip4_addr2(ipaddr) (((const uint8_t*)(&(ipaddr)->addr))[1]) +#define eoe_ip4_addr3(ipaddr) (((const uint8_t*)(&(ipaddr)->addr))[2]) +#define eoe_ip4_addr4(ipaddr) (((const uint8_t*)(&(ipaddr)->addr))[3]) + +/** Set an IP address given by the four byte-parts */ +#define EOE_IP4_ADDR_TO_U32(ipaddr,a,b,c,d) \ + (ipaddr)->addr = EOE_HTONL(EOE_MAKEU32(a,b,c,d)) + +/** Header frame info 1 */ +#define EOE_HDR_FRAME_TYPE_OFFSET 0 +#define EOE_HDR_FRAME_TYPE (0xF << 0) +#define EOE_HDR_FRAME_TYPE_SET(x) (((x) & 0xF) << 0) +#define EOE_HDR_FRAME_TYPE_GET(x) (((x) >> 0) & 0xF) +#define EOE_HDR_FRAME_PORT_OFFSET 4 +#define EOE_HDR_FRAME_PORT (0xF << 4) +#define EOE_HDR_FRAME_PORT_SET(x) (((x) & 0xF) << 4) +#define EOE_HDR_FRAME_PORT_GET(x) (((x) >> 4) & 0xF) +#define EOE_HDR_LAST_FRAGMENT_OFFSET 8 +#define EOE_HDR_LAST_FRAGMENT (0x1 << 8) +#define EOE_HDR_LAST_FRAGMENT_SET(x) (((x) & 0x1) << 8) +#define EOE_HDR_LAST_FRAGMENT_GET(x) (((x) >> 8) & 0x1) +#define EOE_HDR_TIME_APPEND_OFFSET 9 +#define EOE_HDR_TIME_APPEND (0x1 << 9) +#define EOE_HDR_TIME_APPEND_SET(x) (((x) & 0x1) << 9) +#define EOE_HDR_TIME_APPEND_GET(x) (((x) >> 9) & 0x1) +#define EOE_HDR_TIME_REQUEST_OFFSET 10 +#define EOE_HDR_TIME_REQUEST (0x1 << 10) +#define EOE_HDR_TIME_REQUEST_SET(x) (((x) & 0x1) << 10) +#define EOE_HDR_TIME_REQUEST_GET(x) (((x) >> 10) & 0x1) + +/** Header frame info 2 */ +#define EOE_HDR_FRAG_NO_OFFSET 0 +#define EOE_HDR_FRAG_NO (0x3F << 0) +#define EOE_HDR_FRAG_NO_SET(x) (((x) & 0x3F) << 0) +#define EOE_HDR_FRAG_NO_GET(x) (((x) >> 0) & 0x3F) +#define EOE_HDR_FRAME_OFFSET_OFFSET 6 +#define EOE_HDR_FRAME_OFFSET (0x3F << 6) +#define EOE_HDR_FRAME_OFFSET_SET(x) (((x) & 0x3F) << 6) +#define EOE_HDR_FRAME_OFFSET_GET(x) (((x) >> 6) & 0x3F) +#define EOE_HDR_FRAME_NO_OFFSET 12 +#define EOE_HDR_FRAME_NO (0xF << 12) +#define EOE_HDR_FRAME_NO_SET(x) (((x) & 0xF) << 12) +#define EOE_HDR_FRAME_NO_GET(x) (((x) >> 12) & 0xF) + +/** EOE param */ +#define EOE_PARAM_OFFSET 4 +#define EOE_PARAM_MAC_INCLUDE (0x1 << 0) +#define EOE_PARAM_IP_INCLUDE (0x1 << 1) +#define EOE_PARAM_SUBNET_IP_INCLUDE (0x1 << 2) +#define EOE_PARAM_DEFAULT_GATEWAY_INCLUDE (0x1 << 3) +#define EOE_PARAM_DNS_IP_INCLUDE (0x1 << 4) +#define EOE_PARAM_DNS_NAME_INCLUDE (0x1 << 5) + +/** EoE frame types */ +#define EOE_FRAG_DATA 0 +#define EOE_INIT_RESP_TIMESTAMP 1 +#define EOE_INIT_REQ 2 /* Spec SET IP REQ */ +#define EOE_INIT_RESP 3 /* Spec SET IP RESP */ +#define EOE_SET_ADDR_FILTER_REQ 4 +#define EOE_SET_ADDR_FILTER_RESP 5 +#define EOE_GET_IP_PARAM_REQ 6 +#define EOE_GET_IP_PARAM_RESP 7 +#define EOE_GET_ADDR_FILTER_REQ 8 +#define EOE_GET_ADDR_FILTER_RESP 9 + +/** Define number of ports available.(Only one is supported currently */ +#define EOE_NUMBER_OF_PORTS 1 +#define EOE_PORT_INDEX(x) ((x > 0) ? (x - 1) : 0) +/** DNS length according to ETG 1000.6 */ +#define EOE_DNS_NAME_LENGTH 32 +/** Ethernet address length not including VLAN */ +#define EOE_ETHADDR_LENGTH 6 +/** IPv4 address length */ +#define EOE_IP4_LENGTH sizeof(uint32_t) + +/** EOE ip4 address in network order */ +struct eoe_ip4_addr { + uint32_t addr; +}; +typedef struct eoe_ip4_addr eoe_ip4_addr_t; + +/** EOE ethernet address */ +CC_PACKED_BEGIN +typedef struct CC_PACKED eoe_ethaddr +{ + uint8_t addr[EOE_ETHADDR_LENGTH]; +} eoe_ethaddr_t; +CC_PACKED_END + +typedef struct +{ + /** Pointer to current RX buffer to fill */ + eoe_pbuf_t rxebuf; + /** Pointer to current TX buff to Send */ + eoe_pbuf_t txebuf; + + /** Current RX fragment number */ + uint8_t rxfragmentno; + /** Complete RX frame size of current frame */ + uint16_t rxframesize; + /** Current RX data offset in frame */ + uint16_t rxframeoffset; + /** Current RX frame number */ + uint16_t rxframeno; + + /** Current TX fragment number */ + uint8_t txfragmentno; + /** Complete TX frame size of current frame */ + uint16_t txframesize; + /** Current TX data offset in frame */ + uint16_t txframeoffset; +} _EOEvar; + +/** EoE IP request structure */ +typedef struct eoe_param +{ + uint8_t mac_set:1; + uint8_t ip_set:1; + uint8_t subnet_set:1; + uint8_t default_gateway_set:1; + uint8_t dns_ip_set:1; + uint8_t dns_name_set:1; + eoe_ethaddr_t mac; + eoe_ip4_addr_t ip; + eoe_ip4_addr_t subnet; + eoe_ip4_addr_t default_gateway; + eoe_ip4_addr_t dns_ip; + char dns_name[EOE_DNS_NAME_LENGTH]; +} eoe_param_t; + +/** Main EoE status data array. Structure gets filled with current information + * variables during EoE receive and send operations. + */ +static _EOEvar EOEvar; + +/** Main FoE configuration pointer data array. Structure is allocated and filled + * by the application defining what preferences it requires. + */ +static eoe_cfg_t * eoe_cfg; + +/** Local EoE variable holding cached IP information values. + * To be set or read from the user application, eg. TCP/IP stack. + */ +static eoe_param_t nic_ports[EOE_NUMBER_OF_PORTS]; + +/** Local init/reset functions on frame receive init */ +static void EOE_init_rx (); +/** Local init/reset functions on frame send completion */ +static void EOE_init_tx (); + +/** EoE utility function to convert uint32 to eoe ip bytes. + * @param[in] ip = ip in uint32 + * @param[out] byte_ip = eoe ip 4th octet, 3ed octet, 2nd octet, 1st octet + */ +static void EOE_ip_uint32_to_byte (eoe_ip4_addr_t * ip, uint8_t * byte_ip) +{ + byte_ip[3] = eoe_ip4_addr1(ip); /* 1st octet */ + byte_ip[2] = eoe_ip4_addr2(ip); /* 2nd octet */ + byte_ip[1] = eoe_ip4_addr3(ip); /* 3ed octet */ + byte_ip[0] = eoe_ip4_addr4(ip); /* 4th octet */ +} + +/** EoE utility function to convert eoe ip bytes to uint32. + * @param[in] byte_ip = eoe ip 4th octet, 3ed octet, 2nd octet, 1st octet + * @param[out] ip = ip in uint32 + */ +static void EOE_ip_byte_to_uint32 (uint8_t * byte_ip, eoe_ip4_addr_t * ip) +{ + EOE_IP4_ADDR_TO_U32(ip, + byte_ip[3], /* 1st octet */ + byte_ip[2], /* 2nd octet */ + byte_ip[1], /* 3ed octet */ + byte_ip[0]) ;/* 4th octet */ +} + +/** Get EoE cached MAC address + * + * @param[in] port = get MAC for port + * @param[out] mac = variable to store mac in, should fit EOE_ETHADDR_LENGTH + * @return 0= if we succeed, -1 if not set + */ +int EOE_get_mac(uint8_t port, uint8_t mac[]) +{ + int ret = -1; + int port_ix; + + if(port < EOE_NUMBER_OF_PORTS) + { + port_ix = EOE_PORT_INDEX(port); + if(nic_ports[port_ix].mac_set) + { + memcpy(mac, nic_ports[port_ix].mac.addr, + sizeof(nic_ports[port_ix].mac)); + nic_ports[port_ix].mac_set = 1; + ret = 0; + } + } + return ret; +} + +/** Set EoE cached MAC address + * + * @param[in] port = get MAC for port + * @param[in] mac = mac address to store + * @return 0= if we succeed, else -1. + */ +int EOE_ecat_set_mac(uint8_t port, uint8_t mac[]) +{ + int ret = -1; + int port_ix; + if(port < EOE_NUMBER_OF_PORTS) + { + port_ix = EOE_PORT_INDEX(port); + memcpy(nic_ports[port_ix].mac.addr, mac, + sizeof(nic_ports[port_ix].mac)); + ret = 0; + } + return ret; +} + +/** Get EoE cached ip address + * + * @param[in] port = get ip address for port + * @param[out] ip = variable to store ip in + * @return 0= if we succeed, -1 if not set + */ +int EOE_ecat_get_ip(uint8_t port, uint32_t * ip) +{ + int ret = -1; + int port_ix; + if(port < EOE_NUMBER_OF_PORTS) + { + port_ix = EOE_PORT_INDEX(port); + if(nic_ports[port_ix].ip_set) + { + *ip = EOE_NTOHL(nic_ports[port_ix].ip.addr); + ret = 0; + } + } + return ret; +} + +/** Set EoE cached ip address + * + * @param[in] port = get ip for port + * @param[in] ip = ip address to store + * @return 0= if we succeed, else -1. + */ +int EOE_ecat_set_ip(uint8_t port, uint32_t ip) +{ + int ret = -1; + int port_ix; + if(port < EOE_NUMBER_OF_PORTS) + { + port_ix = EOE_PORT_INDEX(port); + nic_ports[port_ix].ip.addr = EOE_HTONL(ip); + nic_ports[port_ix].ip_set = 1; + ret = 0; + } + return ret; +} + +/** Get EoE cached subnet ip address + * + * @param[in] port = get ip address for port + * @param[out] subnet = variable to store ip in + * @return 0= if we succeed, -1 if not set + */ +int EOE_ecat_get_subnet(uint8_t port, uint32_t * subnet) +{ + int ret = -1; + int port_ix; + if(port < EOE_NUMBER_OF_PORTS) + { + port_ix = EOE_PORT_INDEX(port); + if(nic_ports[port_ix].subnet_set) + { + *subnet = EOE_NTOHL(nic_ports[port_ix].subnet.addr); + ret = 0; + } + } + return ret; +} + +/** Set EoE cached subnet ip address + * + * @param[in] port = get ip for port + * @param[in] subnet = ip address to store + * @return 0= if we succeed, else -1. + */ +int EOE_ecat_set_subnet(uint8_t port, uint32_t subnet) +{ + int ret = -1; + int port_ix; + if(port < EOE_NUMBER_OF_PORTS) + { + port_ix = EOE_PORT_INDEX(port); + nic_ports[port_ix].subnet.addr = EOE_HTONL(subnet); + nic_ports[port_ix].subnet_set = 1; + ret = 0; + } + return ret; +} + +/** Get EoE cached default gateway ip address + * + * @param[in] port = get ip address for port + * @param[out] default_gateway = variable to store ip in + * @return 0= if we succeed, -1 if not set + */ +int EOE_ecat_get_gateway(uint8_t port, uint32_t * default_gateway) +{ + int ret = -1; + int port_ix; + if(port < EOE_NUMBER_OF_PORTS) + { + port_ix = EOE_PORT_INDEX(port); + if(nic_ports[port_ix].default_gateway_set) + { + *default_gateway = + EOE_NTOHL(nic_ports[port_ix].default_gateway.addr); + ret = 0; + } + } + return ret; +} + +/** Set EoE cached default gateway ip address + * + * @param[in] port = get ip for port + * @param[in] default_gateway = ip address to store + * @return 0= if we succeed, else -1. + */ +int EOE_ecat_set_gateway(uint8_t port, uint32_t default_gateway) +{ + int ret = -1; + int port_ix; + if(port < EOE_NUMBER_OF_PORTS) + { + port_ix = EOE_PORT_INDEX(port); + nic_ports[port_ix].default_gateway.addr = + EOE_HTONL(default_gateway); + nic_ports[port_ix].default_gateway_set = 1; + ret = 0; + } + return ret; +} + +/** Get EoE cached dns ip address + * + * @param[in] port = get ip address for port + * @param[out] dns_ip = variable to store ip in + * @return 0= if we succeed, -1 if not set + */ +int EOE_ecat_get_dns_ip(uint8_t port, uint32_t * dns_ip) +{ + int ret = -1; + int port_ix; + if(port < EOE_NUMBER_OF_PORTS) + { + port_ix = EOE_PORT_INDEX(port); + if(nic_ports[port_ix].dns_ip_set) + { + *dns_ip = EOE_NTOHL(nic_ports[port_ix].dns_ip.addr); + ret = 0; + } + } + return ret; +} + +/** Set EoE cached dns ip address + * + * @param[in] port = get ip for port + * @param[in] dns_ip = ip address to store + * @return 0= if we succeed, else -1. + */ +int EOE_ecat_set_dns_ip(uint8_t port, uint32_t dns_ip) +{ + int ret = -1; + int port_ix; + if(port < EOE_NUMBER_OF_PORTS) + { + port_ix = EOE_PORT_INDEX(port); + nic_ports[port_ix].dns_ip.addr = EOE_HTONL(dns_ip); + nic_ports[port_ix].dns_ip_set = 1; + ret = 0; + } + return ret; +} + +/** Get EoE cached dns name + * + * @param[in] port = get dns name for port + * @param[out] dns_name = variable to store dns name in + * @return 0= if we succeed, -1 if not set + */ +int EOE_ecat_get_dns_name(uint8_t port, char * dns_name) +{ + int ret = -1; + int port_ix; + if(port < EOE_NUMBER_OF_PORTS) + { + port_ix = EOE_PORT_INDEX(port); + if(nic_ports[port_ix].dns_name_set) + { + memcpy(dns_name, + nic_ports[port_ix].dns_name, + sizeof(nic_ports[port_ix].dns_name)); + ret = 0; + } + } + return ret; +} + +/** Set EoE cached dns name + * + * @param[in] port = get dns name for port + * @param[in] dns_name = dns name to store + * @return 0= if we succeed, else -1. + */ +int EOE_ecat_set_dns_name(uint8_t port, char * dns_name) +{ + int ret = -1; + int port_ix; + + if(port < EOE_NUMBER_OF_PORTS) + { + port_ix = EOE_PORT_INDEX(port); + memcpy(nic_ports[port_ix].dns_name, + dns_name, + sizeof(nic_ports[port_ix].dns_name)); + nic_ports[port_ix].dns_name_set = 1; + ret = 0; + } + return ret; +} + +/** Function for sending an simple EOE response frame. + * + * @param[in] frametype1 = frame type of response + * @param[in] result = result code + */ +static void EOE_no_data_response (uint16_t frameinfo1, uint16_t result) +{ + _EOE *eoembx; + uint8_t mbxhandle; + + /* Send back a response packet. */ + mbxhandle = ESC_claimbuffer (); + if (mbxhandle) + { + eoembx = (_EOE *) &MBX[mbxhandle * ESC_MBXSIZE]; + eoembx->mbxheader.length = htoes (ESC_EOEHSIZE); + eoembx->mbxheader.mbxtype = MBXEOE; + eoembx->eoeheader.frameinfo1 = htoes(frameinfo1); + eoembx->eoeheader.result = htoes(result); + MBXcontrol[mbxhandle].state = MBXstate_outreq; + } +} + +/** EoE get IP param request handler. Will send a get IP param response. + */ +static void EOE_get_ip (void) +{ + _EOE *req_eoembx; + _EOE *eoembx; + uint8_t mbxhandle; + uint16_t frameinfo1; + uint8_t port; + uint8_t flags; + uint8_t data_offset; + int port_ix; + + req_eoembx = (_EOE *) &MBX[0]; + frameinfo1 = etohs(req_eoembx->eoeheader.frameinfo1); + port = EOE_HDR_FRAME_PORT_GET(frameinfo1); + data_offset = EOE_PARAM_OFFSET; + flags = 0; + + if(port > EOE_NUMBER_OF_PORTS) + { + DPRINT("Invalid port\n"); + /* Return error response on given port */ + EOE_no_data_response((EOE_HDR_FRAME_PORT_SET(port) | + EOE_INIT_RESP | + EOE_HDR_LAST_FRAGMENT), + EOE_RESULT_UNSPECIFIED_ERROR); + return; + } + + /* Refresh settings if needed */ + if(eoe_cfg->load_eth_settings != NULL) + { + (void)eoe_cfg->load_eth_settings(); + } + + /* Send back an response packet. */ + mbxhandle = ESC_claimbuffer (); + if (mbxhandle) + { + eoembx = (_EOE *) &MBX[mbxhandle * ESC_MBXSIZE]; + eoembx->mbxheader.mbxtype = MBXEOE; + MBXcontrol[mbxhandle].state = MBXstate_outreq; + eoembx->eoeheader.frameinfo1 = + htoes(EOE_HDR_FRAME_TYPE_SET(EOE_GET_IP_PARAM_RESP) | + EOE_HDR_FRAME_PORT_SET(port) | + EOE_HDR_LAST_FRAGMENT); + eoembx->eoeheader.frameinfo2 = 0; + + /* include mac in get ip request */ + port_ix = EOE_PORT_INDEX(port); + if(nic_ports[port_ix].mac_set) + { + flags |= EOE_PARAM_MAC_INCLUDE; + memcpy(&eoembx->data[data_offset] , + nic_ports[port_ix].mac.addr, + EOE_ETHADDR_LENGTH); + /* Add size of mac address */ + data_offset += EOE_ETHADDR_LENGTH; + + } + /* include ip in get ip request */ + if(nic_ports[port_ix].ip_set) + { + flags |= EOE_PARAM_IP_INCLUDE; + EOE_ip_uint32_to_byte(&nic_ports[port_ix].ip, + &eoembx->data[data_offset]); + /* Add size of uint32 IP address */ + data_offset += EOE_IP4_LENGTH; + } + + /* include subnet in get ip request */ + if(nic_ports[port_ix].subnet_set) + { + flags |= EOE_PARAM_SUBNET_IP_INCLUDE; + EOE_ip_uint32_to_byte(&nic_ports[port_ix].subnet, + &eoembx->data[data_offset]); + /* Add size of uint32 IP address */ + data_offset += EOE_IP4_LENGTH; + } + + /* include default gateway in get ip request */ + if(nic_ports[port_ix].default_gateway_set) + { + flags |= EOE_PARAM_DEFAULT_GATEWAY_INCLUDE; + EOE_ip_uint32_to_byte(&nic_ports[port_ix].default_gateway, + &eoembx->data[data_offset]); + /* Add size of uint32 IP address */ + data_offset += EOE_IP4_LENGTH; + } + /* include dns ip in get ip request */ + if(nic_ports[port_ix].dns_ip_set) + { + flags |= EOE_PARAM_DNS_IP_INCLUDE; + EOE_ip_uint32_to_byte(&nic_ports[port_ix].dns_ip, + &eoembx->data[data_offset]); + /* Add size of uint32 IP address */ + data_offset += EOE_IP4_LENGTH; + } + + /* include dns name in get ip request */ + if(nic_ports[port_ix].dns_name_set) + { + /* TwinCAT include EOE_DNS_NAME_LENGTH chars even if name is shorter */ + flags |= EOE_PARAM_DNS_NAME_INCLUDE; + memcpy(&eoembx->data[data_offset], + nic_ports[port_ix].dns_name, + EOE_DNS_NAME_LENGTH); + /* Add size of dns name length */ + data_offset += EOE_DNS_NAME_LENGTH; + } + + eoembx->data[0] = flags; + eoembx->mbxheader.length = htoes (ESC_EOEHSIZE + data_offset); + } +} + +/** EoE set IP param request handler. Will send a set IP param response. + */ +static void EOE_set_ip (void) +{ + _EOE *eoembx; + uint16_t eoedatasize; + uint16_t frameinfo1; + uint8_t port; + uint8_t flags; + uint8_t data_offset; + uint16_t result; + int port_ix; + + eoembx = (_EOE *) &MBX[0]; + eoedatasize = etohs(eoembx->mbxheader.length) - ESC_EOEHSIZE; + frameinfo1 = etohs(eoembx->eoeheader.frameinfo1); + port = EOE_HDR_FRAME_PORT_GET(frameinfo1); + flags = eoembx->data[0]; + data_offset = EOE_PARAM_OFFSET; + + if(port > EOE_NUMBER_OF_PORTS) + { + DPRINT("Invalid port\n"); + /* Return error response on given port */ + EOE_no_data_response((EOE_HDR_FRAME_PORT_SET(port) | + EOE_INIT_RESP | + EOE_HDR_LAST_FRAGMENT), + EOE_RESULT_UNSPECIFIED_ERROR); + return; + } + + /* mac included in set ip request? */ + port_ix = EOE_PORT_INDEX(port); + if(flags & EOE_PARAM_MAC_INCLUDE) + { + memcpy(&nic_ports[port_ix].mac.addr, + &eoembx->data[data_offset], + EOE_ETHADDR_LENGTH); + nic_ports[port_ix].mac_set = 1; + /* Add size of mac address */ + data_offset += EOE_ETHADDR_LENGTH; + } + /* ip included in set ip request? */ + if(flags & EOE_PARAM_IP_INCLUDE) + { + EOE_ip_byte_to_uint32(&eoembx->data[data_offset], + &nic_ports[port_ix].ip); + nic_ports[port_ix].ip_set = 1; + /* Add size of uint32 IP address */ + data_offset += EOE_IP4_LENGTH; + } + /* subnet included in set ip request? */ + if(flags & EOE_PARAM_SUBNET_IP_INCLUDE) + { + EOE_ip_byte_to_uint32(&eoembx->data[data_offset], + &nic_ports[port_ix].subnet); + nic_ports[port_ix].subnet_set = 1; + /* Add size of uint32 IP address */ + data_offset += EOE_IP4_LENGTH; + } + /* default gateway included in set ip request? */ + if(flags & EOE_PARAM_DEFAULT_GATEWAY_INCLUDE) + { + EOE_ip_byte_to_uint32(&eoembx->data[data_offset], + &nic_ports[port_ix].default_gateway); + nic_ports[port_ix].default_gateway_set = 1; + /* Add size of uint32 IP address */ + data_offset += EOE_IP4_LENGTH; + } + /* dns ip included in set ip request? */ + if(flags & EOE_PARAM_DNS_IP_INCLUDE) + { + EOE_ip_byte_to_uint32(&eoembx->data[data_offset], + &nic_ports[port_ix].dns_ip); + nic_ports[port_ix].dns_ip_set = 1; + /* Add size of uint32 IP address */ + data_offset += EOE_IP4_LENGTH; + } + /* dns name included in set ip request? */ + if(flags & EOE_PARAM_DNS_NAME_INCLUDE) + { + uint16_t dns_len = MIN((eoedatasize - data_offset), EOE_DNS_NAME_LENGTH); + memcpy(nic_ports[port_ix].dns_name, + &eoembx->data[data_offset], + dns_len); + nic_ports[port_ix].dns_name_set = 1; + data_offset += dns_len; /* expected 1- EOE_DNS_NAME_LENGTH; */ + } + + if(data_offset > eoedatasize) + { + result = MBXERR_SIZETOOSHORT; + } + else + { + /* Application specific store settings function. From there + * you typically set the IP for the TCP/IP stack */ + if(eoe_cfg->store_ethernet_settings != NULL) + { + result = eoe_cfg->store_ethernet_settings(); + } + else + { + result = EOE_RESULT_NO_IP_SUPPORT; + } + } + EOE_no_data_response((EOE_HDR_FRAME_PORT_SET(port) | + EOE_INIT_RESP | + EOE_HDR_LAST_FRAGMENT), + result); +} + +/** EoE receive fragment handler. + */ +static void EOE_receive_fragment (void) +{ + _EOE *eoembx; + eoembx = (_EOE *) &MBX[0]; + uint16_t eoedatasize = etohs(eoembx->mbxheader.length) - ESC_EOEHSIZE; + uint16_t frameinfo1 = etohs(eoembx->eoeheader.frameinfo1); + uint16_t frameinfo2 = etohs(eoembx->eoeheader.frameinfo2); + + /* Capture error case */ + if(EOEvar.rxfragmentno != EOE_HDR_FRAG_NO_GET(frameinfo2)) + { + DPRINT("Unexpected fragment number %d, expected: %d\n", + EOE_HDR_FRAG_NO_GET(frameinfo2), EOEvar.rxfragmentno); + /* Clean up existing saved data */ + if(EOEvar.rxfragmentno != 0) + { + EOE_init_rx(); + } + /* Skip fragment if not start of new frame */ + if(EOE_HDR_FRAG_NO_GET(frameinfo2) > 0) + { + return; + } + } + + /* Start of new frame at fragment 0 */ + if(EOEvar.rxfragmentno == 0) + { + EOEvar.rxframesize = (EOE_HDR_FRAME_OFFSET_GET(frameinfo2) << 5); + + if(EOEvar.rxebuf.payload != NULL) + { + EOEvar.rxebuf.len = EOEvar.rxframesize; + EOEvar.rxframeoffset = 0; + EOEvar.rxframeno = EOE_HDR_FRAME_NO_GET(frameinfo2); + } + } + /* In frame fragment received */ + else + { + uint16_t offset = (EOE_HDR_FRAME_OFFSET_GET(frameinfo2) << 5); + /* Validate received fragment */ + if(EOEvar.rxframeno != EOE_HDR_FRAME_NO_GET(frameinfo2)) + { + DPRINT("Unexpected frame number %d, expected: %d\n", + EOE_HDR_FRAME_NO_GET(frameinfo2), EOEvar.rxframeno); + EOE_init_rx (); + return; + } + else if(EOEvar.rxframeoffset != offset) + { + DPRINT("Unexpected frame offset %d, expected: %d\n", + offset, EOEvar.rxframeoffset); + EOE_init_rx (); + return; + } + } + + /* Check so allocated buffer is sufficient */ + if ((EOEvar.rxframeoffset + eoedatasize) <= EOEvar.rxframesize) + { + memcpy((uint8_t *)(EOEvar.rxebuf.payload + EOEvar.rxframeoffset), + eoembx->data, + eoedatasize); + EOEvar.rxframeoffset += eoedatasize; + EOEvar.rxfragmentno++; + } + else + { + DPRINT("Size of data exceed available buffer size\n"); + EOE_init_rx (); + return; + } + + if(EOE_HDR_LAST_FRAGMENT_GET(frameinfo1)) + { + /* Remove time stamp, TODO support for time stamp? */ + if(EOE_HDR_TIME_APPEND_GET(frameinfo1)) + { + EOEvar.rxframeoffset -= 4; + } + EOEvar.rxebuf.len = EOEvar.rxframeoffset; + eoe_cfg->handle_recv_buffer(EOE_HDR_FRAME_PORT_GET(frameinfo1), + &EOEvar.rxebuf); + /* Pass ownership of buf to receive function */ + EOEvar.rxebuf.payload = NULL; + EOE_init_rx (); + } +} + +/** EoE send fragment handler. + */ +static void EOE_send_fragment () +{ + _EOE *eoembx; + uint8_t mbxhandle; + int len; + int len_to_send; + uint16_t frameinfo1; + uint16_t frameinfo2; + static uint8_t frameno = 0; + + /* Do we have a current transfer on-going */ + if(EOEvar.txebuf.payload == NULL) + { + /* Fetch a buffer if available */ + len = eoe_cfg->fetch_send_buffer(0, &EOEvar.txebuf); + if(len > 0) + { + EOEvar.txframesize = len; + } + else + { + return; + } + } + + /* Process the frame if we can get a free mailbox */ + mbxhandle = ESC_claimbuffer (); + if (mbxhandle) + { + len_to_send = EOEvar.txframesize - EOEvar.txframeoffset; + if((len_to_send + ESC_EOEHSIZE + ESC_MBXHSIZE) > ESC_MBXSIZE) + { + /* Adjust to len in whole 32 octet blocks to fit specification*/ + len_to_send = + (((ESC_MBXSIZE - ESC_EOEHSIZE - ESC_MBXHSIZE) >> 5) << 5); + } + + /* TODO: port handling? */ + if(len_to_send == (EOEvar.txframesize - EOEvar.txframeoffset)) + { + frameinfo1 = EOE_HDR_LAST_FRAGMENT_SET(1); + } + else + { + frameinfo1 = 0; + } + + /* Set fragment number */ + frameinfo2 = EOE_HDR_FRAG_NO_SET(EOEvar.txfragmentno); + + /* Set complete size for fragment 0 or offset for in frame fragments */ + if(EOEvar.txfragmentno > 0) + { + frameinfo2 |= (EOE_HDR_FRAME_OFFSET_SET((EOEvar.txframeoffset >> 5))); + } + else + { + frameinfo2 |= + (EOE_HDR_FRAME_OFFSET_SET(((EOEvar.txframesize + 31) >> 5))); + frameno++; + } + + /* Set frame number */ + frameinfo2 = frameinfo2 | EOE_HDR_FRAME_NO_SET(frameno); + + eoembx = (_EOE *) &MBX[mbxhandle * ESC_MBXSIZE]; + eoembx->mbxheader.length = htoes (len_to_send + ESC_EOEHSIZE); + eoembx->mbxheader.mbxtype = MBXEOE; + eoembx->eoeheader.frameinfo1 = htoes(frameinfo1); + eoembx->eoeheader.frameinfo2 = htoes(frameinfo2); + + /* Copy data to mailbox */ + memcpy(eoembx->data, + &EOEvar.txebuf.payload[EOEvar.txframeoffset], + len_to_send); + MBXcontrol[mbxhandle].state = MBXstate_outreq; + + /* Did we complete the frame? */ + if(len_to_send == (EOEvar.txframesize - EOEvar.txframeoffset)) + { + EOE_init_tx (); + } + else + { + EOEvar.txframeoffset += len_to_send; + EOEvar.txfragmentno += 1; + } + if(eoe_cfg->fragment_sent_event != NULL) + { + eoe_cfg->fragment_sent_event(); + } + } +} + +/** Initialize by clearing all current status variables and fetch new buffer. + */ +static void EOE_init_rx () +{ + /* Reset RX transfer status variables */ + EOEvar.rxfragmentno = 0; + EOEvar.rxframesize = 0; + EOEvar.rxframeoffset = 0; + EOEvar.rxframeno = 0; + + /* Fetch buffer */ + if(EOEvar.rxebuf.payload == NULL) + { + if(eoe_cfg->get_buffer != NULL) + { + /* TODO: verify size VS buffer size */ + eoe_cfg->get_buffer(&EOEvar.rxebuf); + } + } +} + +/** Initialize by clearing all current status variables and release old buffer. + */ +static void EOE_init_tx () +{ + /* Reset TX transfer status variables */ + EOEvar.txfragmentno = 0; + EOEvar.txframesize = 0; + EOEvar.txframeoffset = 0; + + /* Release what seems as an abandoned buffer */ + if((EOEvar.txebuf.payload != NULL)) + { + if(eoe_cfg->free_buffer != NULL) + { + eoe_cfg->free_buffer(&EOEvar.txebuf); + EOEvar.txebuf.pbuf = NULL; + EOEvar.txebuf.payload = NULL; + EOEvar.txebuf.len = 0; + } + } +} + +/** Initialize by clearing all current status variables. + */ +void EOE_init () +{ + DPRINT("EOE_init\n"); + EOE_init_tx (); + EOE_init_rx (); +} + +/** Function copying the application configuration variable + * to the EoE module local pointer variable. + * + * @param[in] cfg = Pointer to by the Application static declared + * configuration variable holding application specific details. + */ +void EOE_config (eoe_cfg_t * cfg) +{ + eoe_cfg = cfg; +} + +/** Main EoE receive function checking the status on current mailbox buffers + * carrying data, distributing the mailboxes to appropriate EOE functions + * depending on requested frametype. + */ +void ESC_eoeprocess (void) +{ + _MBXh *mbh; + _EOE *eoembx; + uint16_t frameinfo1; + + if (ESCvar.MBXrun == 0) + { + return; + } + if (!ESCvar.xoe && (MBXcontrol[0].state == MBXstate_inclaim)) + { + mbh = (_MBXh *) &MBX[0]; + if (mbh->mbxtype == MBXEOE) + { + ESCvar.xoe = MBXEOE; + } + } + if (ESCvar.xoe == MBXEOE) + { + eoembx = (_EOE *) &MBX[0]; + /* Verify the size of the file data. */ + if (etohs (eoembx->mbxheader.length) < ESC_EOEHSIZE) + { + EOE_no_data_response ( + EOE_INIT_RESP | EOE_HDR_LAST_FRAGMENT, + MBXERR_SIZETOOSHORT); + } + else + { + frameinfo1 = etohs(eoembx->eoeheader.frameinfo1); + switch (EOE_HDR_FRAME_TYPE_GET(frameinfo1)) + { + case EOE_FRAG_DATA: + { + EOE_receive_fragment (); + break; + } + case EOE_INIT_REQ: + { + EOE_set_ip (); + break; + } + case EOE_GET_IP_PARAM_REQ: + { + EOE_get_ip (); + break; + } + case EOE_INIT_RESP_TIMESTAMP: + case EOE_INIT_RESP: + case EOE_SET_ADDR_FILTER_REQ: + case EOE_SET_ADDR_FILTER_RESP: + case EOE_GET_IP_PARAM_RESP: + case EOE_GET_ADDR_FILTER_REQ: + case EOE_GET_ADDR_FILTER_RESP: + default: + { + DPRINT("EOE_RESULT_UNSUPPORTED_TYPE\n"); + EOE_no_data_response ((EOE_HDR_FRAME_PORT & frameinfo1) | + (EOE_HDR_FRAME_TYPE & frameinfo1) | + EOE_HDR_LAST_FRAGMENT, + EOE_RESULT_UNSUPPORTED_FRAME_TYPE); + break; + } + } + } + MBXcontrol[0].state = MBXstate_idle; + ESCvar.xoe = 0; + } +} +/** EoE function to send a fragment. + * NOTE: Not thread safe, should be called from the SOES task sequential + * with other mailbox functions. Add support for threading by adding + * a thread safe application fetch function, example a mailbox with buffers + * to send, posted by TCP/IP stack and fetched by SOES task. + */ +void ESC_eoeprocess_tx (void) +{ + if (ESCvar.MBXrun == 0) + { + return; + } + EOE_send_fragment (); +} diff --git a/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/esc_eoe.h b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/esc_eoe.h new file mode 100755 index 0000000..78bd74b --- /dev/null +++ b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/esc_eoe.h @@ -0,0 +1,68 @@ +/* + * Licensed under the GNU General Public License version 2 with exceptions. See + * LICENSE file in the project root for full license information + */ + +/** \file + * \brief + * Headerfile for esc_eoe.c + */ + +#ifndef __esc_eoe__ +#define __esc_eoe__ + +#include + +typedef struct eoe_pbuf +{ + /** Pointer to frame buffer type used by a TCP/IP stack. (Not mandatory) */ + void * pbuf; + /** Pointer to frame buffer to send or read from */ + uint8_t * payload; + /** Length of data in frame buffer */ + size_t len; +} eoe_pbuf_t; + +typedef struct eoe_cfg +{ + /** Callback function to get a frame buffer for storage of received frame */ + void (*get_buffer) (eoe_pbuf_t * ebuf); + /** Callback function to free a frame buffer */ + void (*free_buffer) (eoe_pbuf_t * ebuf); + /** Callback function to read local settings and update EtherCAT variables + * to be delivered to the EtherCAT Master + */ + int (*load_eth_settings) (void); + /** Callback function to read settings provided by the EtherCAT master + * and store to local settings. + */ + int (*store_ethernet_settings) (void); + /** Callback to frame receive function in TCP(IP stack, + * caller should free the buffer + * */ + void (*handle_recv_buffer) (uint8_t port, eoe_pbuf_t * ebuf); + /** Callback to fetch a buffer to send */ + int (*fetch_send_buffer) (uint8_t port, eoe_pbuf_t * ebuf); + /** Callback to notify the application fragment sent */ + void (*fragment_sent_event) (void); +} eoe_cfg_t; + +int EOE_ecat_get_mac (uint8_t port, uint8_t mac[]); +int EOE_ecat_get_ip (uint8_t port, uint32_t * ip); +int EOE_ecat_get_subnet (uint8_t port, uint32_t * subnet); +int EOE_ecat_get_gateway (uint8_t port, uint32_t * default_gateway); +int EOE_ecat_get_dns_ip (uint8_t port, uint32_t * dns_ip); +int EOE_ecat_get_dns_name (uint8_t port, char * dns_name); +int EOE_ecat_set_mac (uint8_t port, uint8_t mac[]); +int EOE_ecat_set_ip (uint8_t port, uint32_t ip); +int EOE_ecat_set_subnet (uint8_t port, uint32_t subnet); +int EOE_ecat_set_gateway (uint8_t port, uint32_t default_gateway); +int EOE_ecat_set_dns_ip (uint8_t port, uint32_t dns_ip); +int EOE_ecat_set_dns_name (uint8_t port, char * dns_name); + +void EOE_config (eoe_cfg_t * cfg); +void EOE_init (void); +void ESC_eoeprocess (void); +void ESC_eoeprocess_tx (void); + +#endif diff --git a/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/esc_foe.c b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/esc_foe.c new file mode 100755 index 0000000..475f460 --- /dev/null +++ b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/esc_foe.c @@ -0,0 +1,627 @@ +/* + * Licensed under the GNU General Public License version 2 with exceptions. See + * LICENSE file in the project root for full license information + */ + + /** \file + * \brief + * File over EtherCAT (FoE) module. + */ + + +#include +#include "esc.h" +#include "esc_foe.h" +#include + + /** \file + * \brief + * File over EtherCAT (FoE) module. + * + * FOE read / write and FOE service functions + */ + +//define if FOE_read should be supported +//#define FOE_READ_SUPPORTED + +/** Variable holding current filename read at FOE Open. + */ +static char foe_file_name[FOE_FN_MAX + 1]; + + +/** Main FoE configuration pointer data array. Structure is allocated and filled + * by the application defining what preferences it requires. + */ +static foe_cfg_t * foe_cfg; +/** Pointer to current file configuration item used by FoE. + */ +static foe_file_cfg_t * foe_file; +/** Main FoE status data array. Structure gets filled with current status + * variables during FoE usage. + */ +static _FOEvar FOEvar; + +/** Validate a write or read request by checking filename and password. + * + * @param[in] name = Filename + * @param[in] num_chars = Length of filename + * @param[in] pass = Numeric variable of password + * @param[in] op = Request op-code + * @return 0= if we succeed, FOE_ERR_NOTFOUND something wrong with filename or + * password + */ +static int FOE_fopen (char *name, uint8_t num_chars, uint32_t pass, uint8_t op) +{ + uint32_t i; + + /* Unpack the file name into characters we can look at. */ + if (num_chars > FOE_FN_MAX) + { + num_chars = FOE_FN_MAX; + } + + for (i = 0; i < num_chars; i++) + { + foe_file_name[i] = name[i]; + } + foe_file_name[i] = '\0'; + + /* Figure out what file they're talking about. */ + for (i = 0; i < foe_cfg->n_files; i++) + { + if (0 == strncmp (foe_file_name, foe_cfg->files[i].name, num_chars)) + { + if (pass != foe_cfg->files[i].filepass) + { + return FOE_ERR_NORIGHTS; + } + + if (op == FOE_OP_WRQ && + (foe_cfg->files[i].write_only_in_boot) && + (ESCvar.ALstatus != ESCboot)) + { + return FOE_ERR_NOTINBOOTSTRAP; + } + + foe_file = &foe_cfg->files[i]; + foe_file->address_offset = 0; + foe_file->total_size = 0; + switch (op) + { + case FOE_OP_RRQ: + { + FOEvar.fposition = 0; + FOEvar.fend = foe_cfg->files[i].max_data; + return 0; + } + case FOE_OP_WRQ: + { + FOEvar.fposition = 0; + FOEvar.fend = foe_cfg->files[i].max_data; + return 0; + } + } + } + } + + return FOE_ERR_NOTFOUND; +} + +#ifdef FOE_READ_SUPPORTED +/** Function writing local data to mailbox buffer to be sent as next FoE frame. + * It will try to fill the Mailbox buffer available if there is enough data + * left to read. + * + * @param[in] data = pointer to buffer + * @param[in] maxlength = max length of data possible to read, controlled by + * Mailbox - FoE and Mailbox frame headers. + + * @return Number of copied bytes. + */ +static uint16_t FOE_fread (uint8_t * data, uint16_t maxlength) +{ + uint16_t ncopied = 0; + + while (maxlength && (FOEvar.fend - FOEvar.fposition)) + { + maxlength--; + *(data++) = foe_cfg->fbuffer[FOEvar.fposition++]; + ncopied++; + } + + return ncopied; +} +#endif + +/** Function reading mailbox buffer to local buffer to be handled by + * application write hook. Ex. flash routine used by software update. + * It will consume the buffer and call the write hook every time the configured + * flush buffer limit is reached. + * + * + * @param[in] data = Pointer to buffer + * @param[in] length = Length of data to read + + * @return Number of copied bytes. + */ +static uint16_t FOE_fwrite (uint8_t *data, uint16_t length) +{ + uint16_t ncopied = 0; + uint32_t failed = 0; + + DPRINT("FOE_fwrite\n"); + FOEvar.fprevposition = FOEvar.fposition; + while (length && (FOEvar.fend - FOEvar.fposition) && !failed) + { + length--; + foe_cfg->fbuffer[FOEvar.fbufposition++] = *(data++); + if(FOEvar.fbufposition >= foe_cfg->buffer_size) + { + failed = foe_file->write_function (foe_file, foe_cfg->fbuffer, FOEvar.fbufposition); + FOEvar.fbufposition = 0; + foe_file->address_offset += foe_cfg->buffer_size; + } + FOEvar.fposition++; + ncopied++; + } + + foe_file->total_size += ncopied; + + DPRINT("FOE_fwrite END with : %d\n",ncopied); + return ncopied; +} + + +/** Function handling the final FOE_fwrite when we close up regardless + * if we have filled the buffers or not. + * + * @return Number of copied bytes on success, 0= if failed. + */ +static uint32_t FOE_fclose (void) +{ + uint32_t failed = 0; + + DPRINT("FOE_fclose\n"); + + failed = foe_file->write_function (foe_file, foe_cfg->fbuffer, FOEvar.fbufposition); + foe_file->address_offset += FOEvar.fbufposition; + FOEvar.fbufposition = 0; + + return failed; +} + +/** Initialize by clearing all current status variables. + * + */ +void FOE_init () +{ + DPRINT("FOE_init\n"); + FOEvar.foepacket = 0; + FOEvar.foestate = FOE_READY; + FOEvar.fposition = 0; + FOEvar.fprevposition = 0; + FOEvar.fbufposition = 0; +} + +/** Function for sending an FOE abort frame. + * + * @param[in] code = abort code + */ +static void FOE_abort (uint32_t code) +{ + _FOE *foembx; + uint8_t mbxhandle; + + if (code) + { + /* Send back an error packet. */ + mbxhandle = ESC_claimbuffer (); + if (mbxhandle) + { + foembx = (_FOE *) &MBX[mbxhandle * ESC_MBXSIZE]; + foembx->mbxheader.length = htoes (ESC_FOEHSIZE); /* Don't bother with error text for now. */ + foembx->mbxheader.mbxtype = MBXFOE; + foembx->foeheader.opcode = FOE_OP_ERR; + foembx->foeheader.errorcode = htoel (code); + MBXcontrol[mbxhandle].state = MBXstate_outreq; + } + /* Nothing we can do if we can't get an outbound mailbox. */ + } + DPRINT("FOE_abort: 0x%X\n", code); + FOE_init (); +} + +#ifdef FOE_READ_SUPPORTED +/** Sends an FoE data frame, returning the number of data bytes + * written or an error number. + * Error numbers will be greater than FOE_DATA_SIZE. + + * @param[in] data = pointer to buffer + * @param[in] length = length of data to read + + * @return Number of data bytes written or an error number. Error numbers + * will be greater than FOE_DATA_SIZE. + */ +static int FOE_send_data_packet () +{ + _FOE *foembx; + uint16_t data_len; + uint8_t mbxhandle; + + mbxhandle = ESC_claimbuffer (); + if (mbxhandle) + { + foembx = (_FOE *) &MBX[mbxhandle * ESC_MBXSIZE]; + data_len = FOE_fread (foembx->data, ESC_FOE_DATA_SIZE); + foembx->foeheader.opcode = FOE_OP_DATA; + foembx->foeheader.packetnumber = htoel (FOEvar.foepacket); + FOEvar.foepacket++; + foembx->mbxheader.length = htoes (data_len + ESC_FOEHSIZE); + foembx->mbxheader.mbxtype = MBXFOE; + /* Mark the outbound mailbox as filled. */ + MBXcontrol[mbxhandle].state = MBXstate_outreq; + return data_len; + } + else + { + return FOE_ERR_PROGERROR; + } +} +#endif + +/** Sends an FoE ack data frame. + + * @return 0= or error number. + */ +static int FOE_send_ack () +{ + _FOE *foembx; + uint8_t mbxhandle; + + mbxhandle = ESC_claimbuffer (); + if (mbxhandle) + { + DPRINT("FOE_send_ack\n"); + foembx = (_FOE *) &MBX[mbxhandle * ESC_MBXSIZE]; + foembx->mbxheader.length = htoes (ESC_FOEHSIZE); + foembx->mbxheader.mbxtype = MBXFOE; + foembx->foeheader.opcode = FOE_OP_ACK; + foembx->foeheader.packetnumber = htoel (FOEvar.foepacket); + FOEvar.foepacket++; + MBXcontrol[mbxhandle].state = MBXstate_outreq; + return 0; + } + else + { + DPRINT("ERROR:FOE_send_ack\n"); + return FOE_ERR_PROGERROR; + } +} + +/* Handlers for various FoE states. */ + +#ifdef FOE_READ_SUPPORTED +/** FoE read request handler. Starts with Initialize, Open and Sending one frame. + * When first frame have been sent we will send data from Ack. + * On error we will send FOE Abort. + * + */ +static void FOE_read () +{ + _FOE *foembx; + uint32_t data_len; + uint32_t password; + int res; + + if (FOEvar.foestate != FOE_READY) + { + FOE_abort (FOE_ERR_ILLEGAL); + return; + } + + FOE_init (); + foembx = (_FOE *) &MBX[0]; + /* Get the length of the file name in octets. */ + data_len = etohs (foembx->mbxheader.length) - ESC_FOEHSIZE; + password = etohl (foembx->foeheader.password); + + res = FOE_fopen (foembx->filename, data_len, password, FOE_OP_RRQ); + if (res == 0) + { + FOEvar.foepacket = 1; + /* + * Attempt to send the packet + */ + res = FOE_send_data_packet (); + if (res <= (int)ESC_FOE_DATA_SIZE) + { + FOEvar.foestate = FOE_WAIT_FOR_ACK; + } + else + { + FOE_abort (res); + } + } + else + { + FOE_abort (res); + } +} + +/** FoE data ack handler. Will continue sending next frame until finished. + * On error we will send FOE Abort. + */ +static void FOE_ack () +{ + int res; + + /* Make sure we're able to take this. */ + if (FOEvar.foestate == FOE_WAIT_FOR_FINAL_ACK) + { + /* Move us back to ready state. */ + FOE_init (); + return; + } + else if (FOEvar.foestate != FOE_WAIT_FOR_ACK) + { + FOE_abort (FOE_ERR_ILLEGAL); + return; + } + res = FOE_send_data_packet (); + if (res < (int)ESC_FOE_DATA_SIZE) + { + FOEvar.foestate = FOE_WAIT_FOR_FINAL_ACK; + } + else if (res >= FOE_ERR_NOTDEFINED) + { + FOE_abort (FOE_ERR_PROGERROR); + } +} +#endif + +/** FoE write request handler. Starts with Initialize, Open and Ack that we can/will + * receive data. On error we will send FOE Abort. + * + */ +static void FOE_write () +{ + _FOE *foembx; + uint32_t data_len; + uint32_t password; + int res; + + if (FOEvar.foestate != FOE_READY) + { + FOE_abort (FOE_ERR_ILLEGAL); + return; + } + + FOE_init (); + foembx = (_FOE *) &MBX[0]; + data_len = etohs (foembx->mbxheader.length) - ESC_FOEHSIZE; + password = etohl (foembx->foeheader.password); + + /* Get an address we can write the file to, if possible. */ + res = FOE_fopen (foembx->filename, data_len, password, FOE_OP_WRQ); + DPRINT("%s %sOK, file \"%s\"\n", __func__, (res == 0) ? "" : "N", foe_file_name); + if (res == 0) + { + res = FOE_send_ack (); + if (res) + { + FOE_abort (res); + } + else + { + FOEvar.foestate = FOE_WAIT_FOR_DATA; + } + } + else + { + FOE_abort (res); + } +} +/** FoE data request handler. Validates and reads data until we're finished. Every + * read frame followed by an Ack frame. On error we will send FOE Abort. + * + */ +static void FOE_data () +{ + _FOE *foembx; + uint32_t packet; + uint16_t data_len, ncopied; + int res; + + if(FOEvar.foestate != FOE_WAIT_FOR_DATA) + { + FOE_abort(FOE_ERR_ILLEGAL); + return; + } + + foembx = (_FOE*)&MBX[0]; + data_len = etohs(foembx->mbxheader.length) - ESC_FOEHSIZE; + packet = etohl(foembx->foeheader.packetnumber); + + if (packet != FOEvar.foepacket) + { + DPRINT("FOE_data packet error, packet: %d, foeheader.packet: %d\n",packet,FOEvar.foepacket); + FOE_abort (FOE_ERR_PACKETNO); + } + else if (data_len == 0) + { + DPRINT("FOE_data completed\n"); + FOE_fclose (); + res = FOE_send_ack (); + FOE_init (); + } + else if (FOEvar.fposition + data_len > FOEvar.fend) + { + DPRINT("FOE_data disk full\n"); + FOE_abort (FOE_ERR_DISKFULL); + } + else + { + ncopied = FOE_fwrite (foembx->data, data_len); + if (!ncopied) + { + DPRINT("FOE_data no copied\n"); + FOE_abort (FOE_ERR_PROGERROR); + } + else if (data_len == ESC_FOE_DATA_SIZE) + { + DPRINT("FOE_data data_len == FOE_DATA_SIZE\n"); + if (ncopied != data_len) + { + DPRINT("FOE_data only %d of %d copied\n",ncopied, data_len); + FOE_abort (FOE_ERR_PROGERROR); + } + res = FOE_send_ack (); + if (res) + { + FOE_abort (res); + } + } + else + { + if ((ncopied != data_len) || FOE_fclose ()) + { + DPRINT("FOE_fclose failed to write extra buffer\n"); + FOE_abort (FOE_ERR_PROGERROR); + } + else + { + DPRINT("FOE_data completed\n"); + res = FOE_send_ack (); + FOE_init (); + } + } + } +} + +#ifdef FOE_READ_SUPPORTED +/** FoE read request busy handler. Send an Ack of last frame again. On error + * we will send FOE Abort. + * + */ +static void FOE_busy () +{ + /* Only valid if we're servicing a read request. */ + if (FOEvar.foestate != FOE_WAIT_FOR_ACK) + { + FOE_abort (FOE_ERR_ILLEGAL); + } + else + { + /* Send the last part again. */ + FOEvar.fposition = FOEvar.fprevposition; + FOEvar.foepacket--; + FOE_ack (); + } +} +#endif + +/** FoE error requesthandler. Send an FOE Abort. + * + */ +static void FOE_error () +{ + /* Master panic! abort the transfer. */ + FOE_abort (0); +} + +/** Function copying the application configuration variable + * to the FoE module local pointer variable. + * + * @param[in] cfg = Pointer to by the Application static declared + * configuration variable holding application specific details. + */ +void FOE_config (foe_cfg_t * cfg) +{ + foe_cfg = cfg; +} + +/** Main FoE function checking the status on current mailbox buffers carrying + * data, distributing the mailboxes to appropriate FOE functions depending + * on requested opcode. + * On Error an FoE Error or FoE Abort will be sent. + */ +void ESC_foeprocess (void) +{ + _MBXh *mbh; + _FOE *foembx; + + if (ESCvar.MBXrun == 0) + { + return; + } + if (!ESCvar.xoe && (MBXcontrol[0].state == MBXstate_inclaim)) + { + mbh = (_MBXh *) &MBX[0]; + if (mbh->mbxtype == MBXFOE) + { + ESCvar.xoe = MBXFOE; + } + } + if (ESCvar.xoe == MBXFOE) + { + foembx = (_FOE *) &MBX[0]; + /* Verify the size of the file data. */ + if (etohs (foembx->mbxheader.length) < ESC_FOEHSIZE) + { + FOE_abort (MBXERR_SIZETOOSHORT); + } + else + { + switch (foembx->foeheader.opcode) + { + case FOE_OP_WRQ: + { + DPRINT("FOE_OP_WRQ\n"); + FOE_write (); + break; + } + case FOE_OP_DATA: + { + DPRINT("FOE_OP_DATA\n"); + FOE_data (); + break; + } +#ifdef FOE_READ_SUPPORTED + case FOE_OP_RRQ: + { + DPRINT("FOE_OP_RRQ\n"); + FOE_read (); + break; + } + case FOE_OP_ACK: + { + DPRINT("FOE_OP_ACK\n"); + FOE_ack (); + break; + } + + case FOE_OP_BUSY: + { + DPRINT("FOE_OP_BUSY\n"); + FOE_busy (); + break; + } +#endif + case FOE_OP_ERR: + { + DPRINT("FOE_OP_ERR\n"); + FOE_error (); + break; + } + default: + { + DPRINT("FOE_ERR_NOTDEFINED\n"); + FOE_abort (FOE_ERR_NOTDEFINED); + break; + } + } + } + MBXcontrol[0].state = MBXstate_idle; + ESCvar.xoe = 0; + } +} diff --git a/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/esc_foe.h b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/esc_foe.h new file mode 100755 index 0000000..71472c9 --- /dev/null +++ b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/esc_foe.h @@ -0,0 +1,77 @@ +/* + * Licensed under the GNU General Public License version 2 with exceptions. See + * LICENSE file in the project root for full license information + */ + +/** \file + * \brief + * Headerfile for esc_foe.c + */ + +#ifndef __esc_foe__ +#define __esc_foe__ + +#include + +/** Maximum number of characters allowed in a file name. */ +#define FOE_FN_MAX 31 + +typedef struct foe_file_cfg foe_file_cfg_t; +struct foe_file_cfg +{ + /** Name of file to receive from master */ + const char * name; + /** Size of file,sizeof data we can recv */ + uint32_t max_data; + /** Where to store the data initially */ + uint32_t dest_start_address; + /** Current address during write of file */ + uint32_t address_offset; + /** Calculated size of file received */ + uint32_t total_size; + /** FoE password */ + uint32_t filepass; + /** This file can be written only in BOOT state. Intended for FW files */ + uint8_t write_only_in_boot; + /** for feature use */ + uint32_t padding:24; + /** Pointer to application foe write function */ + uint32_t (*write_function) (foe_file_cfg_t * self, uint8_t * data, size_t length); +}; + +typedef struct foe_cfg +{ + /** Allocate static in caller func to fit buffer_size */ + uint8_t * fbuffer; + /** Buffer size before we flush to destination */ + uint32_t buffer_size; + /** Number of files used in firmware update */ + uint32_t n_files; + /** Pointer to files configured to be used by FoE */ + foe_file_cfg_t * files; +} foe_cfg_t; + +typedef struct CC_PACKED +{ + /** Current FoE state, ex. Waiting for ACK, Waiting for DATA */ + uint8_t foestate; + /** Current file buffer position, evaluated against foe file buffer size + * when to flush + */ + uint16_t fbufposition; + /** Frame number in read or write sequence */ + uint32_t foepacket; + /** Current position in file to be handled by FoE request */ + uint32_t fposition; + /** Previous position in file to be handled by FoE request */ + uint32_t fprevposition; + /** End position of allocated disk space for FoE requested file */ + uint32_t fend; +} _FOEvar; + +/* Initializes FoE state. */ +void FOE_config (foe_cfg_t * cfg); +void FOE_init (void); +void ESC_foeprocess (void); + +#endif diff --git a/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/hal/arduino-lan9252/esc_hw.c b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/hal/arduino-lan9252/esc_hw.c new file mode 100755 index 0000000..a9f0982 --- /dev/null +++ b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/hal/arduino-lan9252/esc_hw.c @@ -0,0 +1,458 @@ +/* + * Licensed under the GNU General Public License version 2 with exceptions. See + * LICENSE file in the project root for full license information + */ + + /** \file + * \brief + * ESC hardware layer functions for LAN9252. + * + * Function to read and write commands to the ESC. Used to read/write ESC + * registers and memory. + */ +#include "esc.h" +#include "spi.h" +#include + + +#define O_RDWR 1 + +#define BIT(x) 1 << (x) + +#define ESC_CMD_SERIAL_WRITE 0x02 +#define ESC_CMD_SERIAL_READ 0x03 +#define ESC_CMD_FAST_READ 0x0B +#define ESC_CMD_RESET_SQI 0xFF + +#define ESC_CMD_FAST_READ_DUMMY 1 +#define ESC_CMD_ADDR_INC BIT(6) + +#define ESC_PRAM_RD_FIFO_REG 0x000 +#define ESC_PRAM_WR_FIFO_REG 0x020 +#define ESC_PRAM_RD_ADDR_LEN_REG 0x308 +#define ESC_PRAM_RD_CMD_REG 0x30C +#define ESC_PRAM_WR_ADDR_LEN_REG 0x310 +#define ESC_PRAM_WR_CMD_REG 0x314 + +#define ESC_PRAM_CMD_BUSY BIT(31) +#define ESC_PRAM_CMD_ABORT BIT(30) + +#define ESC_PRAM_CMD_CNT(x) ((x >> 8) & 0x1F) +#define ESC_PRAM_CMD_AVAIL BIT(0) + +#define ESC_PRAM_SIZE(x) ((x) << 16) +#define ESC_PRAM_ADDR(x) ((x) << 0) + +#define ESC_CSR_DATA_REG 0x300 +#define ESC_CSR_CMD_REG 0x304 + +#define ESC_CSR_CMD_BUSY BIT(31) +#define ESC_CSR_CMD_READ (BIT(31) | BIT(30)) +#define ESC_CSR_CMD_WRITE BIT(31) +#define ESC_CSR_CMD_SIZE(x) (x << 16) + +#define ESC_RESET_CTRL_REG 0x1F8 +#define ESC_RESET_CTRL_RST BIT(6) +#define ESC_DIGITAL_RST 0x00000001 + +#define ESC_ID_REV_REG 0x050 +#define LAN9252_ID_REV 0x9252 + +#define ESC_IRQ_CFG_REG 0x054 +#define ESC_INT_EN_REG 0x05C +#define ESC_BYTE_TEST_REG 0x064 +#define ESC_TEST_VALUE 0x87654321 + +#define ESC_HW_CFG_REG 0x074 +#define ESC_READY BIT(27) + + +static int lan9252 = -1; + +/* lan9252 singel write */ +static void lan9252_write_32 (uint16_t address, uint32_t val) +{ + uint8_t data[7]; + + data[0] = ESC_CMD_SERIAL_WRITE; + data[1] = ((address >> 8) & 0xFF); + data[2] = (address & 0xFF); + data[3] = (val & 0xFF); + data[4] = ((val >> 8) & 0xFF); + data[5] = ((val >> 16) & 0xFF); + data[6] = ((val >> 24) & 0xFF); + + /* Select device. */ + spi_select (lan9252); + /* Write data */ + write (lan9252, data, sizeof(data)); + /* Un-select device. */ + spi_unselect (lan9252); +} + +/* lan9252 single read */ +static uint32_t lan9252_read_32 (uint32_t address) +{ + uint8_t data[4]; + uint8_t result[4]; + + data[0] = ESC_CMD_FAST_READ; + data[1] = ((address >> 8) & 0xFF); + data[2] = (address & 0xFF); + data[3] = ESC_CMD_FAST_READ_DUMMY; + + /* Select device. */ + spi_select (lan9252); + /* Read data */ + write (lan9252, data, sizeof(data)); + read (lan9252, result, sizeof(result)); + /* Un-select device. */ + spi_unselect (lan9252); + + return ((result[3] << 24) | + (result[2] << 16) | + (result[1] << 8) | + result[0]); +} + +/* ESC read CSR function */ +static void ESC_read_csr (uint16_t address, void *buf, uint16_t len) +{ + uint32_t value; + + value = (ESC_CSR_CMD_READ | ESC_CSR_CMD_SIZE(len) | address); + lan9252_write_32(ESC_CSR_CMD_REG, value); + + do + { + value = lan9252_read_32(ESC_CSR_CMD_REG); + } while(value & ESC_CSR_CMD_BUSY); + + value = lan9252_read_32(ESC_CSR_DATA_REG); + memcpy(buf, (uint8_t *)&value, len); +} + +/* ESC write CSR function */ +static void ESC_write_csr (uint16_t address, void *buf, uint16_t len) +{ + uint32_t value; + + memcpy((uint8_t*)&value, buf,len); + lan9252_write_32(ESC_CSR_DATA_REG, value); + value = (ESC_CSR_CMD_WRITE | ESC_CSR_CMD_SIZE(len) | address); + lan9252_write_32(ESC_CSR_CMD_REG, value); + + do + { + value = lan9252_read_32(ESC_CSR_CMD_REG); + } while(value & ESC_CSR_CMD_BUSY); +} + +/* ESC read process data ram function */ +/*static*/ void ESC_read_pram (uint16_t address, void *buf, uint16_t len) +{ + uint32_t value; + uint8_t * temp_buf = (uint8_t *)buf; + uint16_t byte_offset = 0; + uint8_t fifo_cnt, first_byte_position, temp_len, data[4]; + + value = ESC_PRAM_CMD_ABORT; + lan9252_write_32(ESC_PRAM_RD_CMD_REG, value); + + do + { + value = lan9252_read_32(ESC_PRAM_RD_CMD_REG); + }while(value & ESC_PRAM_CMD_BUSY); + + value = ESC_PRAM_SIZE(len) | ESC_PRAM_ADDR(address); + lan9252_write_32(ESC_PRAM_RD_ADDR_LEN_REG, value); + + value = ESC_PRAM_CMD_BUSY; + lan9252_write_32(ESC_PRAM_RD_CMD_REG, value); + + do + { + value = lan9252_read_32(ESC_PRAM_RD_CMD_REG); + }while((value & ESC_PRAM_CMD_AVAIL) == 0); + + /* Fifo count */ + fifo_cnt = ESC_PRAM_CMD_CNT(value); + + /* Read first value from FIFO */ + value = lan9252_read_32(ESC_PRAM_RD_FIFO_REG); + fifo_cnt--; + + /* Find out first byte position and adjust the copy from that + * according to LAN9252 datasheet and MicroChip SDK code + */ + first_byte_position = (address & 0x03); + temp_len = ((4 - first_byte_position) > len) ? len : (4 - first_byte_position); + + memcpy(temp_buf ,((uint8_t *)&value + first_byte_position), temp_len); + len -= temp_len; + byte_offset += temp_len; + + /* Select device. */ + spi_select (lan9252); + /* Send command and address for fifo read */ + data[0] = ESC_CMD_FAST_READ; + data[1] = ((ESC_PRAM_RD_FIFO_REG >> 8) & 0xFF); + data[2] = (ESC_PRAM_RD_FIFO_REG & 0xFF); + data[3] = ESC_CMD_FAST_READ_DUMMY; + write (lan9252, data, sizeof(data)); + + /* Continue reading until we have read len */ + while(len > 0) + { + temp_len = (len > 4) ? 4: len; + /* Always read 4 byte */ + read (lan9252, (temp_buf + byte_offset), sizeof(uint32_t)); + + fifo_cnt--; + len -= temp_len; + byte_offset += temp_len; + } + /* Un-select device. */ + spi_unselect (lan9252); +} + +/* ESC write process data ram function */ +/* static */ void ESC_write_pram (uint16_t address, void *buf, uint16_t len) +{ + uint32_t value; + uint8_t * temp_buf = (uint8_t *)buf; + uint16_t byte_offset = 0; + uint8_t fifo_cnt, first_byte_position, temp_len, data[3]; + + value = ESC_PRAM_CMD_ABORT; + lan9252_write_32(ESC_PRAM_WR_CMD_REG, value); + + do + { + value = lan9252_read_32(ESC_PRAM_WR_CMD_REG); + }while(value & ESC_PRAM_CMD_BUSY); + + value = ESC_PRAM_SIZE(len) | ESC_PRAM_ADDR(address); + lan9252_write_32(ESC_PRAM_WR_ADDR_LEN_REG, value); + + value = ESC_PRAM_CMD_BUSY; + lan9252_write_32(ESC_PRAM_WR_CMD_REG, value); + + do + { + value = lan9252_read_32(ESC_PRAM_WR_CMD_REG); + }while((value & ESC_PRAM_CMD_AVAIL) == 0); + + /* Fifo count */ + fifo_cnt = ESC_PRAM_CMD_CNT(value); + + /* Find out first byte position and adjust the copy from that + * according to LAN9252 datasheet + */ + first_byte_position = (address & 0x03); + temp_len = ((4 - first_byte_position) > len) ? len : (4 - first_byte_position); + + memcpy(((uint8_t *)&value + first_byte_position), temp_buf, temp_len); + + /* Write first value from FIFO */ + lan9252_write_32(ESC_PRAM_WR_FIFO_REG, value); + + len -= temp_len; + byte_offset += temp_len; + fifo_cnt--; + + /* Select device. */ + spi_select (lan9252); + /* Send command and address for incrementing write */ + data[0] = ESC_CMD_SERIAL_WRITE; + data[1] = ((ESC_PRAM_WR_FIFO_REG >> 8) & 0xFF); + data[2] = (ESC_PRAM_WR_FIFO_REG & 0xFF); + write (lan9252, data, sizeof(data)); + + /* Continue reading until we have read len */ + while(len > 0) + { + temp_len = (len > 4) ? 4 : len; + value = 0; + memcpy((uint8_t *)&value, (temp_buf + byte_offset), temp_len); + /* Always write 4 byte */ + write (lan9252, (uint8_t *)&value, sizeof(value)); + + fifo_cnt--; + len -= temp_len; + byte_offset += temp_len; + } + /* Un-select device. */ + spi_unselect (lan9252); +} + + +/** ESC read function used by the Slave stack. + * + * @param[in] address = address of ESC register to read + * @param[out] buf = pointer to buffer to read in + * @param[in] len = number of bytes to read + */ +void ESC_read (uint16_t address, void *buf, uint16_t len) +{ + /* Select Read function depending on address, process data ram or not */ + if (address >= 0x1000) + { + ESC_read_pram(address, buf, len); + } + else + { + uint16_t size; + uint8_t *temp_buf = (uint8_t *)buf; + + while(len > 0) + { + /* We write maximum 4 bytes at the time */ + size = (len > 4) ? 4 : len; + /* Make size aligned to address according to LAN9252 datasheet + * Table 12-14 EtherCAT CSR Address VS size and MicroChip SDK code + */ + /* If we got an odd address size is 1 , 01b 11b is captured */ + if(address & BIT(0)) + { + size = 1; + } + /* If address 1xb and size != 1 and 3 , allow size 2 else size 1 */ + else if (address & BIT(1)) + { + size = (size & BIT(0)) ? 1 : 2; + } + /* size 3 not valid */ + else if (size == 3) + { + size = 1; + } + /* else size is kept AS IS */ + ESC_read_csr(address, temp_buf, size); + + /* next address */ + len -= size; + temp_buf += size; + address += size; + } + } + /* To mimic the ET1100 always providing AlEvent on every read or write */ + ESC_read_csr(ESCREG_ALEVENT,(void *)&ESCvar.ALevent,sizeof(ESCvar.ALevent)); + ESCvar.ALevent = etohs (ESCvar.ALevent); + +} + +/** ESC write function used by the Slave stack. + * + * @param[in] address = address of ESC register to write + * @param[out] buf = pointer to buffer to write from + * @param[in] len = number of bytes to write + */ +void ESC_write (uint16_t address, void *buf, uint16_t len) +{ + /* Select Write function depending on address, process data ram or not */ + if (address >= 0x1000) + { + ESC_write_pram(address, buf, len); + } + else + { + uint16_t size; + uint8_t *temp_buf = (uint8_t *)buf; + + while(len > 0) + { + /* We write maximum 4 bytes at the time */ + size = (len > 4) ? 4 : len; + /* Make size aligned to address according to LAN9252 datasheet + * Table 12-14 EtherCAT CSR Address VS size and MicroChip SDK code + */ + /* If we got an odd address size is 1 , 01b 11b is captured */ + if(address & BIT(0)) + { + size = 1; + } + /* If address 1xb and size != 1 and 3 , allow size 2 else size 1 */ + else if (address & BIT(1)) + { + size = (size & BIT(0)) ? 1 : 2; + } + /* size 3 not valid */ + else if (size == 3) + { + size = 1; + } + /* else size is kept AS IS */ + ESC_write_csr(address, temp_buf, size); + + /* next address */ + len -= size; + temp_buf += size; + address += size; + } + } + + /* To mimic the ET1x00 always providing AlEvent on every read or write */ + ESC_read_csr(ESCREG_ALEVENT,(void *)&ESCvar.ALevent,sizeof(ESCvar.ALevent)); + ESCvar.ALevent = etohs (ESCvar.ALevent); +} + +/* Un-used due to evb-lan9252-digio not havning any possability to + * reset except over SPI. + */ +void ESC_reset (void) +{ + +} + + +uint8_t ESC_IsLAN9252() +{ + volatile uint32_t value; + uint16_t detectedChip, revision; + + /* Read */ + value = lan9252_read_32(ESC_ID_REV_REG); + detectedChip = value >> 16; + revision = value & 0xFF; + + return detectedChip == LAN9252_ID_REV + && revision >= 1; +} + + +void ESC_init (const esc_cfg_t * config) +{ + uint32_t value; + + spi_setup(); + + /* Reset the ecat core here due to evb-lan9252-digio not having any GPIO + * for that purpose. + */ + lan9252_write_32(ESC_RESET_CTRL_REG, ESC_DIGITAL_RST); + do + { + value = lan9252_read_32(ESC_RESET_CTRL_REG); + } + while(value & ESC_RESET_CTRL_RST); + + /* Read test register */ + do + { + value = lan9252_read_32(ESC_BYTE_TEST_REG); + } + while (value != ESC_TEST_VALUE); + + /* Check Ready flag */ + do + { + value = lan9252_read_32(ESC_HW_CFG_REG); + } + while ((value & ESC_READY) == 0); + + if(!ESC_IsLAN9252()) + { + while (1); + } +} diff --git a/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/hal/arduino-lan9252/spi.cpp b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/hal/arduino-lan9252/spi.cpp new file mode 100755 index 0000000..8752019 --- /dev/null +++ b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/hal/arduino-lan9252/spi.cpp @@ -0,0 +1,66 @@ +#include "spi.hpp" +// #include +#include + + +char SCS = ESC_GPIO_Pin_CS; + + +void spi_setup(void) +{ + SPI.begin(); + pinMode(SCS, OUTPUT); + spi_unselect(0); + delay(100); + SPI.beginTransaction(SPISettings(SPIX_ESC_SPEED, MSBFIRST, SPI_MODE0)); + +} + +void spi_select (int8_t board) +{ + // Soft CSN + #if SCS_ACTIVE_POLARITY == SCS_LOW + digitalWrite(SCS, LOW); + #endif +} + +void spi_unselect (int8_t board) +{ + // Soft CSN + #if SCS_ACTIVE_POLARITY == SCS_LOW + digitalWrite(SCS, HIGH); + #endif +} + +inline static uint8_t spi_transfer_byte(uint8_t byte) +{ + return SPI.transfer(byte); + // AVR will need handling last byte transfer difference, + // but then again they pobably wont even fit EtherCAT stack in RAM + // so no need to care for now +} + +void write (int8_t board, uint8_t *data, uint8_t size) +{ + for(int i = 0; i < size; ++i) + { + spi_transfer_byte(data[i]); + } +} + +void read (int8_t board, uint8_t *result, uint8_t size) +{ + for(int i = 0; i < size; ++i) + { + result[i] = spi_transfer_byte(DUMMY_BYTE); + } +} + + +void spi_bidirectionally_transfer (int8_t board, uint8_t *result, uint8_t *data, uint8_t size) +{ + for(int i = 0; i < size; ++i) + { + result[i] = spi_transfer_byte(data[i]); + } +} diff --git a/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/hal/arduino-lan9252/spi.h b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/hal/arduino-lan9252/spi.h new file mode 100755 index 0000000..7413d47 --- /dev/null +++ b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/hal/arduino-lan9252/spi.h @@ -0,0 +1,15 @@ +#ifndef SRC_APP_SPI_H_ +#define SRC_APP_SPI_H_ + +#include + + +void spi_setup(void); +void spi_select (int8_t board); +void spi_unselect (int8_t board); +void write (int8_t board, uint8_t *data, uint8_t size); +void read (int8_t board, uint8_t *result, uint8_t size); +void spi_bidirectionally_transfer (int8_t board, uint8_t *result, uint8_t *data, uint8_t size); + + +#endif /* SRC_APP_SPI_H_ */ diff --git a/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/hal/arduino-lan9252/spi.hpp b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/hal/arduino-lan9252/spi.hpp new file mode 100755 index 0000000..297656c --- /dev/null +++ b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/lib/soes/hal/arduino-lan9252/spi.hpp @@ -0,0 +1,25 @@ +#ifndef SRC_APP_SPI_H_ +#define SRC_APP_SPI_H_ + +#include + +#define SCS_LOW 0 +#define SCS_HIGH 1 +#define SCS_ACTIVE_POLARITY SCS_LOW + +#define SPIX_ESC SPI1 +//#define SPIX_ESC_SPEED 18000000 +#define SPIX_ESC_SPEED 50000000 +#define ESC_GPIO_Pin_CS PC4 + +#define DUMMY_BYTE 0xFF + +extern "C" void spi_setup(void); +extern "C" void spi_select (int8_t board); +extern "C" void spi_unselect (int8_t board); +extern "C" void write (int8_t board, uint8_t *data, uint8_t size); +extern "C" void read (int8_t board, uint8_t *result, uint8_t size); +extern "C" void spi_bidirectionally_transfer (int8_t board, uint8_t *result, uint8_t *data, uint8_t size); + + +#endif /* SRC_APP_SPI_H_ */ diff --git a/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/platformio.ini b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/platformio.ini new file mode 100755 index 0000000..06c389b --- /dev/null +++ b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/platformio.ini @@ -0,0 +1,23 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:genericSTM32F407VGT6] +framework = arduino +platform = ststm32 +board = genericSTM32F407VGT6 +upload_protocol = stlink +debug_tool = stlink +debug_build_flags = -O0 -g -ggdb +monitor_port = COM3 +monitor_filters = send_on_enter, time, colorize, log2file +monitor_speed = 115200 +build_flags = -Wl,--no-warn-rwx-segment +lib_deps = + SPI diff --git a/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/src/extend32to64.cpp b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/src/extend32to64.cpp new file mode 100755 index 0000000..0486008 --- /dev/null +++ b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/src/extend32to64.cpp @@ -0,0 +1,18 @@ +#include "extend32to64.h" + +// Extend from 32-bit to 64-bit precision +int64_t extend32to64::extendTime(uint32_t in) +{ + int64_t c64 = (int64_t)in - HALF_PERIOD; // remove half period to determine (+/-) sign of the wrap + int64_t dif = (c64 - previousTimeValue); // core concept: prev + (current - prev) = current + + // wrap difference from -HALF_PERIOD to HALF_PERIOD. modulo prevents differences after the wrap from having an incorrect result + int64_t mod_dif = ((dif + HALF_PERIOD) % ONE_PERIOD) - HALF_PERIOD; + if (dif < int64_t(-HALF_PERIOD)) + mod_dif += ONE_PERIOD; // account for mod of negative number behavior in C + + int64_t unwrapped = previousTimeValue + mod_dif; + previousTimeValue = unwrapped; // load previous value + + return unwrapped + HALF_PERIOD; // remove the shift we applied at the beginning, and return +} \ No newline at end of file diff --git a/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/src/main.cpp b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/src/main.cpp new file mode 100755 index 0000000..10236fb --- /dev/null +++ b/Cards/EaserCAT-6000-THCAD-reader+Digital-IO/Firmware/src/main.cpp @@ -0,0 +1,237 @@ +#include +#include +extern "C" +{ +#include "ecat_slv.h" +#include "utypes.h" +}; +_Objects Obj; + +#include "extend32to64.h" +extend32to64 longTime; +volatile uint64_t irqTime = 0; + +HardwareSerial Serial1(PA10, PA9); + +uint8_t inputPin[] = {PD15, PD14, PD13, PD12, PD11, PD10, PD9, PD8, PB15, PB14, PB13, PB12}; +uint8_t outputPin[] = {PE10, PE9, PE8, PE7}; + +#include "HardwareTimer.h" +// NOTE This mod in the beginning of HardwareTimer.cpp for 32-bit precision +////// //#define MAX_RELOAD ((1 << 16) - 1) // Currently even 32b timers are used as 16b to have generic behavior +////// #define MAX_RELOAD 0xFFFFFFFF + +#define THCAD_PIN PA0 +// PA0 is connected to Timer 2, a 32-bit timer + +uint32_t channel; +volatile uint32_t FrequencyMeasured, LastCapture = 0, CurrentCapture; +uint32_t input_freq = 0; +volatile uint32_t rolloverCompareCount = 0; +HardwareTimer *EncoderTimer; +void InputCapture_IT_callback(void); +void Rollover_IT_callback(void); + +volatile uint16_t ALEventIRQ; // ALEvent that caused the interrupt + +void cb_set_outputs(void) // Get Master outputs, slave inputs, first operation +{ + // Update digital pins + for (int i = 0; i < sizeof(outputPin); i++) + { + digitalWrite(outputPin[i], Obj.Output[i]); + } +} + +void cb_get_inputs(void) // Set Master inputs, slave outputs, last operation +{ + for (int i = 0; i < sizeof(inputPin); i++) + Obj.Input[i] = digitalRead(inputPin[i]); + + Obj.Velocity = Obj.VelocityScale * FrequencyMeasured; +} + +void ESC_interrupt_enable(uint32_t mask); +void ESC_interrupt_disable(uint32_t mask); +uint16_t dc_checker(void); +void sync0Handler(void); + +static esc_cfg_t config = + { + .user_arg = NULL, + .use_interrupt = 1, + .watchdog_cnt = 150, + .set_defaults_hook = NULL, + .pre_state_change_hook = NULL, + .post_state_change_hook = NULL, + .application_hook = NULL, + .safeoutput_override = NULL, + .pre_object_download_hook = NULL, + .post_object_download_hook = NULL, + .rxpdo_override = NULL, + .txpdo_override = NULL, + .esc_hw_interrupt_enable = ESC_interrupt_enable, + .esc_hw_interrupt_disable = ESC_interrupt_disable, + .esc_hw_eep_handler = NULL, + .esc_check_dc_handler = dc_checker, +}; + +volatile byte serveIRQ = 0; + +volatile uint32_t globalIRQ = 0; +void globalInt(void) +{ + globalIRQ++; +} + +void setup(void) +{ + Serial1.begin(115200); + + for (int i = 0; i < sizeof(inputPin); i++) + pinMode(inputPin[i], INPUT_PULLDOWN); + for (int i = 0; i < sizeof(outputPin); i++) + { + pinMode(outputPin[i], OUTPUT); + digitalWrite(outputPin[i], LOW); + } + + // Automatically retrieve TIM instance and channel associated to pin + // This is used to be compatible with all STM32 series automatically. + TIM_TypeDef *Instance = (TIM_TypeDef *)pinmap_peripheral(digitalPinToPinName(THCAD_PIN), PinMap_PWM); + channel = STM_PIN_CHANNEL(pinmap_function(digitalPinToPinName(THCAD_PIN), PinMap_PWM)); + + EncoderTimer = new HardwareTimer(Instance); + + // Configure rising edge detection to measure frequency + EncoderTimer->setMode(channel, TIMER_INPUT_CAPTURE_RISING, THCAD_PIN); + + // With a PrescalerFactor = 1, the minimum frequency value to measure is : TIM counter clock / CCR MAX + // = (SystemCoreClock) / 65535 + // Example on Nucleo_L476RG with systemClock at 80MHz, the minimum frequency is around 1,2 khz + // To reduce minimum frequency, it is possible to increase prescaler. But this is at a cost of precision. + // The maximum frequency depends on processing of the interruption and thus depend on board used + // Example on Nucleo_L476RG with systemClock at 80MHz the interruption processing is around 4,5 microseconds and thus Max frequency is around 220kHz + uint32_t PrescalerFactor = 1; + EncoderTimer->setPrescaleFactor(PrescalerFactor); + EncoderTimer->setOverflow(0xFFFFFFF0); // Max Period value to have the largest possible time to detect rising edge and avoid timer rollover + EncoderTimer->attachInterrupt(channel, InputCapture_IT_callback); + EncoderTimer->attachInterrupt(Rollover_IT_callback); + EncoderTimer->resume(); + + // Compute this scale factor only once + input_freq = EncoderTimer->getTimerClkFreq() / EncoderTimer->getPrescaleFactor(); + + ecat_slv_init(&config); + attachInterrupt(digitalPinToInterrupt(PC0), globalInt, RISING); +} + +void loop(void) +{ +#if 0 // Sync 0 mode + uint64_t dTime; + if (serveIRQ) + { + DIG_process(ALEventIRQ, DIG_PROCESS_WD_FLAG | DIG_PROCESS_OUTPUTS_FLAG | + DIG_PROCESS_APP_HOOK_FLAG | DIG_PROCESS_INPUTS_FLAG); + serveIRQ = 0; + ESCvar.PrevTime = ESCvar.Time; + ecat_slv_poll(); + } + dTime = longTime.extendTime(micros()) - irqTime; + if (dTime > 5000) // Don't run ecat_slv_poll when expecting to serve interrupt + ecat_slv_poll(); +#else // Freerun mode + ecat_slv(); + +#endif +} + +void sync0Handler(void) +{ + ALEventIRQ = ESC_ALeventread(); + // if (ALEventIRQ & ESCREG_ALEVENT_SM2) + { + irqTime = longTime.extendTime(micros()); + serveIRQ = 1; + } +} + +// Enable SM2 interrupts +void ESC_interrupt_enable(uint32_t mask) +{ + // Enable interrupt for SYNC0 or SM2 or SM3 + uint32_t user_int_mask = ESCREG_ALEVENT_DC_SYNC0 | ESCREG_ALEVENT_SM2 | ESCREG_ALEVENT_SM3; + if (mask & user_int_mask) + { + ESC_ALeventmaskwrite(ESC_ALeventmaskread() | (mask & user_int_mask)); + ESC_ALeventmaskwrite(ESC_ALeventmaskread() & ~(ESCREG_ALEVENT_DC_SYNC0 | ESCREG_ALEVENT_SM3)); + attachInterrupt(digitalPinToInterrupt(PC3), sync0Handler, RISING); + + // Set LAN9252 interrupt pin driver as push-pull active high + uint32_t bits = 0x00000111; + ESC_write(0x54, &bits, 4); + + // Enable LAN9252 interrupt + bits = 0x00000001; + ESC_write(0x5c, &bits, 4); + } +} + +// Disable SM2 interrupts +void ESC_interrupt_disable(uint32_t mask) +{ + // Enable interrupt for SYNC0 or SM2 or SM3 + // uint32_t user_int_mask = ESCREG_ALEVENT_DC_SYNC0 | ESCREG_ALEVENT_SM2 | ESCREG_ALEVENT_SM3; + uint32_t user_int_mask = ESCREG_ALEVENT_SM2; + + if (mask & user_int_mask) + { + // Disable interrupt from SYNC0 + ESC_ALeventmaskwrite(ESC_ALeventmaskread() & ~(mask & user_int_mask)); + detachInterrupt(digitalPinToInterrupt(PC3)); + // Disable LAN9252 interrupt + uint32_t bits = 0x00000000; + ESC_write(0x5c, &bits, 4); + } +} + +extern "C" uint32_t ESC_SYNC0cycletime(void); + +// Setup of DC +uint16_t dc_checker(void) +{ + // Indicate we run DC + ESCvar.dcsync = 1; + return 0; +} + +void InputCapture_IT_callback(void) +{ + CurrentCapture = EncoderTimer->getCaptureCompare(channel); + + /* frequency computation */ + if (CurrentCapture > LastCapture) + { + FrequencyMeasured = input_freq / (CurrentCapture - LastCapture); + } + else if (CurrentCapture <= LastCapture) + { + /* 0xFFFFFFFF is max overflow value */ + FrequencyMeasured = input_freq / (0xFFFFFFFF + CurrentCapture - LastCapture); + } + LastCapture = CurrentCapture; + rolloverCompareCount = 0; +} + +/* In case of timer rollover, frequency is to low to be measured set value to 0 + To reduce minimum frequency, it is possible to increase prescaler. But this is at a cost of precision. */ +void Rollover_IT_callback(void) +{ + rolloverCompareCount++; + + if (rolloverCompareCount > 1) + { + FrequencyMeasured = 0; + } +}