diff --git a/cmvr-es/devices/motor/bus_runtime/CMakeLists.txt b/cmvr-es/devices/motor/bus_runtime/CMakeLists.txt index 238d1841..ce09edab 100644 --- a/cmvr-es/devices/motor/bus_runtime/CMakeLists.txt +++ b/cmvr-es/devices/motor/bus_runtime/CMakeLists.txt @@ -4,7 +4,18 @@ add_library(motor_bus_runtime SHARED ethercat/src/ethercat_motor_bus_runtime.cpp ) -target_include_directories(motor_bus_runtime PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +set(IGH_ETHERCAT_ROOT + ${CMAKE_SOURCE_DIR}/dependency/x86/third_party/ethercat/v1.7.0 +) + +target_include_directories(motor_bus_runtime + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} + PRIVATE + ${IGH_ETHERCAT_ROOT}/include +) + +target_link_directories(motor_bus_runtime PRIVATE ${IGH_ETHERCAT_ROOT}/lib) target_link_libraries(motor_bus_runtime PUBLIC @@ -12,9 +23,28 @@ target_link_libraries(motor_bus_runtime cmvr_es::device::motor_core cmvr_es::mujoco_world PRIVATE + ethercat cmvr_es::device::canbus glog ) add_library(cmvr_es::device::motor_bus_runtime ALIAS motor_bus_runtime) install(TARGETS motor_bus_runtime LIBRARY DESTINATION lib) + +add_executable(ethercat_motor_bus_runtime_real_test + ethercat/src/ethercat_motor_bus_runtime_real_test.cpp +) + +target_include_directories(ethercat_motor_bus_runtime_real_test + PRIVATE + ${CMAKE_SOURCE_DIR}/cmvr-es +) + +target_link_libraries(ethercat_motor_bus_runtime_real_test + PRIVATE + cmvr_es::device::motor_bus_runtime + gtest + gtest_main + pthread + glog +) diff --git a/cmvr-es/devices/motor/bus_runtime/ethercat/include/ethercat_motor_bus_runtime.h b/cmvr-es/devices/motor/bus_runtime/ethercat/include/ethercat_motor_bus_runtime.h index 5d7f8edf..3c6fb5ab 100644 --- a/cmvr-es/devices/motor/bus_runtime/ethercat/include/ethercat_motor_bus_runtime.h +++ b/cmvr-es/devices/motor/bus_runtime/ethercat/include/ethercat_motor_bus_runtime.h @@ -1,10 +1,22 @@ #ifndef CMVR_ES_ETHERCAT_MOTOR_BUS_RUNTIME_H #define CMVR_ES_ETHERCAT_MOTOR_BUS_RUNTIME_H +#include +#include +#include +#include #include +#include +#include #include +#include #include "../../abstract_motor_bus_runtime.h" +#include "motor/bus_runtime/ethercat/include/ethercat_pdo_mapping.h" + +typedef struct ec_domain ec_domain_t; +typedef struct ec_master ec_master_t; +typedef struct ec_slave_config ec_slave_config_t; namespace cmvr::device { @@ -17,12 +29,130 @@ public: const std::string& id() const { return id_; } const config::EtherCATConfig& config() const { return config_; } + void setPdoMapping(EthercatPdoMapping mapping); + const EthercatPdoMapping& pdoMapping() const { return pdo_mapping_; } const config::EthercatSlaveConfig* slaveForMotor(int motor_id) const; + bool hasMotor(int motor_id) const; + + bool hasPdoEntry(int motor_id, std::uint16_t index, std::uint8_t subindex) const; + + template + bool writePdo(int motor_id, std::uint16_t index, std::uint8_t subindex, T value) + { + return writePdoRaw_(motor_id, index, subindex, valueBitLength_(), + toRawValue_(value)); + } + + template + bool readPdo(int motor_id, std::uint16_t index, std::uint8_t subindex, T& value) const + { + std::uint64_t raw = 0; + if (!readPdoRaw_(motor_id, index, subindex, valueBitLength_(), raw)) { + return false; + } + value = fromRawValue_(raw); + return true; + } + + template + bool writeSdo(int motor_id, std::uint16_t index, std::uint8_t subindex, T value) + { + return writeSdoRaw_(motor_id, index, subindex, valueBitLength_(), + toRawValue_(value)); + } + + template + bool readSdo(int motor_id, std::uint16_t index, std::uint8_t subindex, T& value) + { + std::uint64_t raw = 0; + if (!readSdoRaw_(motor_id, index, subindex, valueBitLength_(), raw)) { + return false; + } + value = fromRawValue_(raw); + return true; + } private: + struct PdoEntryRuntime { + EthercatPdoEntryConfig cfg; + unsigned int offset{0}; + bool rx{false}; + std::uint64_t value{0}; + }; + + struct SlaveRuntime { + config::EthercatSlaveConfig cfg; + ec_slave_config_t* slave_config{nullptr}; + std::unordered_map pdo_entries; + }; + + bool configureSlave_(SlaveRuntime& slave); + void cyclicLoop_(); + void readFeedbackLocked_(); + void writeCommandsLocked_(); + void releaseMaster_(); + bool hasValidPdoMapping_() const; + bool writePdoRaw_(int motor_id, std::uint16_t index, std::uint8_t subindex, + std::uint8_t bit_len, std::uint64_t value); + bool readPdoRaw_(int motor_id, std::uint16_t index, std::uint8_t subindex, + std::uint8_t bit_len, std::uint64_t& value) const; + bool writeSdoRaw_(int motor_id, std::uint16_t index, std::uint8_t subindex, + std::uint8_t bit_len, std::uint64_t value); + bool readSdoRaw_(int motor_id, std::uint16_t index, std::uint8_t subindex, + std::uint8_t bit_len, std::uint64_t& value); + + template + static constexpr std::uint8_t valueBitLength_() + { + using ValueType = std::remove_cv_t; + static_assert(std::is_integral_v, "EtherCAT object values must be integral"); + static_assert(!std::is_same_v, "bool is not a valid EtherCAT object value"); + static_assert(sizeof(ValueType) == 1 || sizeof(ValueType) == 2 || sizeof(ValueType) == 4, + "only 8/16/32-bit EtherCAT object values are supported"); + return static_cast(sizeof(ValueType) * 8); + } + + template + static std::uint64_t toRawValue_(T value) + { + using ValueType = std::remove_cv_t; + using UnsignedType = std::make_unsigned_t; + return static_cast(static_cast(value)); + } + + template + static T fromRawValue_(std::uint64_t raw) + { + using ValueType = std::remove_cv_t; + using UnsignedType = std::make_unsigned_t; + const auto unsigned_value = static_cast(raw); + ValueType value{}; + std::memcpy(&value, &unsigned_value, sizeof(ValueType)); + return value; + } + + static std::uint32_t pdoEntryKey_(std::uint16_t index, std::uint8_t subindex); + static std::string hexIndex_(std::uint32_t index); + static std::uint64_t maskValue_(std::uint64_t value, std::uint8_t bit_len); + static bool isSupportedBitLength_(std::uint8_t bit_len); + static std::uint64_t readEntryValue_(const std::uint8_t* domain_data, + const PdoEntryRuntime& entry); + static void writeEntryValue_(std::uint8_t* domain_data, + const PdoEntryRuntime& entry); + std::string id_; config::EtherCATConfig config_; - std::unordered_map slaves_by_motor_id_; + EthercatPdoMapping pdo_mapping_; + std::unordered_map slaves_by_motor_id_; + + ec_master_t* master_{nullptr}; + ec_domain_t* domain_{nullptr}; + std::uint8_t* domain_data_{nullptr}; + + mutable std::mutex data_mutex_; + std::thread cyclic_thread_; + std::atomic running_{false}; + bool initialized_{false}; bool started_{false}; }; diff --git a/cmvr-es/devices/motor/bus_runtime/ethercat/include/ethercat_pdo_mapping.h b/cmvr-es/devices/motor/bus_runtime/ethercat/include/ethercat_pdo_mapping.h new file mode 100644 index 00000000..30db0781 --- /dev/null +++ b/cmvr-es/devices/motor/bus_runtime/ethercat/include/ethercat_pdo_mapping.h @@ -0,0 +1,35 @@ +#ifndef CMVR_ES_ETHERCAT_PDO_MAPPING_H +#define CMVR_ES_ETHERCAT_PDO_MAPPING_H + +#include +#include +#include + +namespace cmvr::device { + +struct EthercatPdoEntryConfig { + std::uint16_t index{0}; + std::uint8_t subindex{0}; + std::uint8_t bit_len{0}; + std::string name; + bool padding{false}; +}; + +struct EthercatPdoConfig { + std::uint16_t index{0}; + std::uint8_t sync_manager{0}; + bool rx{false}; + std::vector entries; +}; + +struct EthercatPdoMapping { + std::uint32_t vendor_id{0}; + std::uint32_t product_code{0}; + std::string name; + std::vector rx_pdos; + std::vector tx_pdos; +}; + +} // namespace cmvr::device + +#endif // CMVR_ES_ETHERCAT_PDO_MAPPING_H diff --git a/cmvr-es/devices/motor/bus_runtime/ethercat/src/ethercat_motor_bus_runtime.cpp b/cmvr-es/devices/motor/bus_runtime/ethercat/src/ethercat_motor_bus_runtime.cpp index 45c44243..6f7102b6 100644 --- a/cmvr-es/devices/motor/bus_runtime/ethercat/src/ethercat_motor_bus_runtime.cpp +++ b/cmvr-es/devices/motor/bus_runtime/ethercat/src/ethercat_motor_bus_runtime.cpp @@ -1,7 +1,17 @@ #include "motor/bus_runtime/ethercat/include/ethercat_motor_bus_runtime.h" +#include +#include +#include +#include +#include +#include +#include + #include "common/base/logging/logger.h" +#include + namespace cmvr::device { bool EthercatMotorBusRuntime::init(const config::MotorGroupConfig& group_cfg) @@ -21,14 +31,15 @@ bool EthercatMotorBusRuntime::init(const config::MotorGroupConfig& group_cfg) } config_ = group_cfg.ethercat(); - if (config_.master_id().empty()) { - CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] master_id is empty: " << id_; - return false; - } if (config_.cycle_us() <= 0) { CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] cycle_us must be positive: " << id_; return false; } + if (!hasValidPdoMapping_()) { + CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] PDO mapping is not configured: " + << id_; + return false; + } slaves_by_motor_id_.clear(); for (const auto& slave : config_.slaves()) { @@ -36,34 +47,95 @@ bool EthercatMotorBusRuntime::init(const config::MotorGroupConfig& group_cfg) CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] invalid motor_id in slave config: " << id_; return false; } - if (slave.slave_index() < 0) { - CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] invalid slave_index for motor " - << slave.motor_id() << " in group: " << id_; - return false; - } if (slaves_by_motor_id_.count(slave.motor_id()) > 0) { CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] duplicate slave motor_id: " << slave.motor_id() << " in group: " << id_; return false; } - slaves_by_motor_id_[slave.motor_id()] = &slave; + SlaveRuntime runtime; + runtime.cfg = slave; + slaves_by_motor_id_.emplace(slave.motor_id(), std::move(runtime)); } + if (slaves_by_motor_id_.empty()) { + CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] no EtherCAT slaves configured: " << id_; + return false; + } + + master_ = ecrt_request_master(config_.master_index()); + if (!master_) { + CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] failed to request EtherCAT master " + << config_.master_index() << ": " << id_; + return false; + } + + domain_ = ecrt_master_create_domain(master_); + if (!domain_) { + CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] failed to create EtherCAT domain: " << id_; + releaseMaster_(); + return false; + } + + for (auto& [motor_id, slave] : slaves_by_motor_id_) { + (void)motor_id; + if (!configureSlave_(slave)) { + releaseMaster_(); + return false; + } + } + + initialized_ = true; return true; } +void EthercatMotorBusRuntime::setPdoMapping(EthercatPdoMapping mapping) +{ + pdo_mapping_ = std::move(mapping); +} + bool EthercatMotorBusRuntime::start() { if (started_) { return true; } - CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] EtherCAT master is not implemented yet: " << id_; - return false; + if (!initialized_ || !master_ || !domain_) { + CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] runtime is not initialized: " << id_; + return false; + } + + if (ecrt_master_activate(master_) != 0) { + CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] failed to activate EtherCAT master: " << id_; + return false; + } + + domain_data_ = ecrt_domain_data(domain_); + if (!domain_data_) { + CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] failed to get EtherCAT domain data: " << id_; + return false; + } + + { + std::lock_guard lock(data_mutex_); + writeCommandsLocked_(); + } + + running_.store(true); + cyclic_thread_ = std::thread(&EthercatMotorBusRuntime::cyclicLoop_, this); + started_ = true; + CMVR_LOG(INFO) << "[EthercatMotorBusRuntime] started EtherCAT runtime: " << id_; + return true; } void EthercatMotorBusRuntime::stop() { + running_.store(false); + if (cyclic_thread_.joinable()) { + cyclic_thread_.join(); + } started_ = false; + initialized_ = false; + domain_data_ = nullptr; + releaseMaster_(); } const config::EthercatSlaveConfig* EthercatMotorBusRuntime::slaveForMotor(const int motor_id) const @@ -72,7 +144,532 @@ const config::EthercatSlaveConfig* EthercatMotorBusRuntime::slaveForMotor(const if (it == slaves_by_motor_id_.end()) { return nullptr; } - return it->second; + return &it->second.cfg; +} + +bool EthercatMotorBusRuntime::hasMotor(const int motor_id) const +{ + return slaves_by_motor_id_.count(motor_id) > 0; +} + +bool EthercatMotorBusRuntime::hasPdoEntry(const int motor_id, + const std::uint16_t index, + const std::uint8_t subindex) const +{ + std::lock_guard lock(data_mutex_); + const auto slave_it = slaves_by_motor_id_.find(motor_id); + if (slave_it == slaves_by_motor_id_.end()) { + return false; + } + return slave_it->second.pdo_entries.count(pdoEntryKey_(index, subindex)) > 0; +} + +bool EthercatMotorBusRuntime::configureSlave_(SlaveRuntime& slave) +{ + slave.slave_config = ecrt_master_slave_config(master_, + static_cast(slave.cfg.alias()), + static_cast(slave.cfg.position()), + pdo_mapping_.vendor_id, + pdo_mapping_.product_code); + if (!slave.slave_config) { + CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] failed to get slave config: group=" << id_ + << ", motor_id=" << slave.cfg.motor_id() + << ", alias=" << slave.cfg.alias() + << ", position=" << slave.cfg.position() + << ", vendor=" << hexIndex_(pdo_mapping_.vendor_id) + << ", product=" << hexIndex_(pdo_mapping_.product_code); + return false; + } + + struct SyncBuild { + bool rx{false}; + std::vector pdo_indices; + std::vector> entry_storage; + std::vector pdo_infos; + }; + + auto add_pdo_to_sync_build = [](std::map& builds, + const EthercatPdoConfig& pdo) { + auto& build = builds[pdo.sync_manager]; + build.rx = pdo.rx; + build.pdo_indices.push_back(pdo.index); + auto& entries = build.entry_storage.emplace_back(); + entries.reserve(pdo.entries.size()); + for (const auto& entry : pdo.entries) { + entries.push_back({entry.index, entry.subindex, entry.bit_len}); + } + }; + + std::map sync_builds; + for (const auto& pdo : pdo_mapping_.rx_pdos) { + add_pdo_to_sync_build(sync_builds, pdo); + } + for (const auto& pdo : pdo_mapping_.tx_pdos) { + add_pdo_to_sync_build(sync_builds, pdo); + } + + for (auto& [sync_manager, build] : sync_builds) { + (void)sync_manager; + build.pdo_infos.reserve(build.entry_storage.size()); + for (std::size_t i = 0; i < build.entry_storage.size(); ++i) { + auto& entries = build.entry_storage[i]; + build.pdo_infos.push_back({ + build.pdo_indices[i], + static_cast(entries.size()), + entries.data(), + }); + } + } + + std::vector sync_infos; + sync_infos.push_back({0, EC_DIR_OUTPUT, 0, nullptr, EC_WD_DISABLE}); + sync_infos.push_back({1, EC_DIR_INPUT, 0, nullptr, EC_WD_DISABLE}); + for (auto& [sync_manager, build] : sync_builds) { + const auto direction = build.rx ? EC_DIR_OUTPUT : EC_DIR_INPUT; + const auto watchdog = build.rx ? EC_WD_ENABLE : EC_WD_DISABLE; + sync_infos.push_back({ + sync_manager, + direction, + static_cast(build.pdo_infos.size()), + build.pdo_infos.data(), + watchdog, + }); + } + sync_infos.push_back({0xff}); + + if (ecrt_slave_config_pdos(slave.slave_config, EC_END, sync_infos.data()) != 0) { + CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] failed to configure PDOs: group=" << id_ + << ", motor_id=" << slave.cfg.motor_id() + << ", position=" << slave.cfg.position(); + return false; + } + + auto register_entry = [&](const EthercatPdoEntryConfig& entry, const bool rx) -> bool { + if (entry.padding) { + return true; + } + if (!isSupportedBitLength_(entry.bit_len)) { + CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] unsupported PDO entry bit length: " + << static_cast(entry.bit_len) + << ", entry=" << hexIndex_(entry.index) + << ":" << static_cast(entry.subindex) + << ", motor_id=" << slave.cfg.motor_id() + << ", group=" << id_; + return false; + } + + const auto key = pdoEntryKey_(entry.index, entry.subindex); + if (slave.pdo_entries.count(key) > 0) { + CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] duplicate PDO entry: " + << hexIndex_(entry.index) + << ":" << static_cast(entry.subindex) + << ", motor_id=" << slave.cfg.motor_id() + << ", group=" << id_; + return false; + } + + const int result = ecrt_slave_config_reg_pdo_entry( + slave.slave_config, entry.index, entry.subindex, domain_, nullptr); + if (result < 0) { + CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] failed to register PDO entry " + << hexIndex_(entry.index) << ":" << static_cast(entry.subindex) + << ", motor_id=" << slave.cfg.motor_id() + << ", group=" << id_; + return false; + } + + PdoEntryRuntime runtime; + runtime.cfg = entry; + runtime.offset = static_cast(result); + runtime.rx = rx; + slave.pdo_entries.emplace(key, std::move(runtime)); + return true; + }; + + for (const auto& pdo : pdo_mapping_.rx_pdos) { + for (const auto& entry : pdo.entries) { + if (!register_entry(entry, true)) { + return false; + } + } + } + for (const auto& pdo : pdo_mapping_.tx_pdos) { + for (const auto& entry : pdo.entries) { + if (!register_entry(entry, false)) { + return false; + } + } + } + + return true; +} + +void EthercatMotorBusRuntime::cyclicLoop_() +{ + const auto period = std::chrono::microseconds(config_.cycle_us()); + auto next_time = std::chrono::steady_clock::now(); + while (running_.load()) { + next_time += period; + + ecrt_master_receive(master_); + ecrt_domain_process(domain_); + { + std::lock_guard lock(data_mutex_); + readFeedbackLocked_(); + writeCommandsLocked_(); + } + ecrt_domain_queue(domain_); + ecrt_master_send(master_); + + std::this_thread::sleep_until(next_time); + } +} + +void EthercatMotorBusRuntime::readFeedbackLocked_() +{ + if (!domain_data_) { + return; + } + for (auto& [motor_id, slave] : slaves_by_motor_id_) { + (void)motor_id; + for (auto& [key, entry] : slave.pdo_entries) { + (void)key; + if (!entry.rx) { + entry.value = readEntryValue_(domain_data_, entry); + } + } + } +} + +void EthercatMotorBusRuntime::writeCommandsLocked_() +{ + if (!domain_data_) { + return; + } + for (const auto& [motor_id, slave] : slaves_by_motor_id_) { + (void)motor_id; + for (const auto& [key, entry] : slave.pdo_entries) { + (void)key; + if (entry.rx) { + writeEntryValue_(domain_data_, entry); + } + } + } +} + +void EthercatMotorBusRuntime::releaseMaster_() +{ + if (master_) { + ecrt_release_master(master_); + } + master_ = nullptr; + domain_ = nullptr; + domain_data_ = nullptr; + for (auto& [motor_id, slave] : slaves_by_motor_id_) { + (void)motor_id; + slave.slave_config = nullptr; + slave.pdo_entries.clear(); + } +} + +bool EthercatMotorBusRuntime::hasValidPdoMapping_() const +{ + return pdo_mapping_.vendor_id != 0 && + pdo_mapping_.product_code != 0 && + !pdo_mapping_.rx_pdos.empty() && + !pdo_mapping_.tx_pdos.empty(); +} + +bool EthercatMotorBusRuntime::writePdoRaw_(const int motor_id, + const std::uint16_t index, + const std::uint8_t subindex, + const std::uint8_t bit_len, + const std::uint64_t value) +{ + std::lock_guard lock(data_mutex_); + auto slave_it = slaves_by_motor_id_.find(motor_id); + if (slave_it == slaves_by_motor_id_.end()) { + return false; + } + auto entry_it = slave_it->second.pdo_entries.find(pdoEntryKey_(index, subindex)); + if (entry_it == slave_it->second.pdo_entries.end()) { + return false; + } + auto& entry = entry_it->second; + if (!entry.rx || entry.cfg.bit_len != bit_len) { + return false; + } + entry.value = maskValue_(value, entry.cfg.bit_len); + if (domain_data_) { + writeEntryValue_(domain_data_, entry); + } + return true; +} + +bool EthercatMotorBusRuntime::readPdoRaw_(const int motor_id, + const std::uint16_t index, + const std::uint8_t subindex, + const std::uint8_t bit_len, + std::uint64_t& value) const +{ + std::lock_guard lock(data_mutex_); + const auto slave_it = slaves_by_motor_id_.find(motor_id); + if (slave_it == slaves_by_motor_id_.end()) { + return false; + } + const auto entry_it = slave_it->second.pdo_entries.find(pdoEntryKey_(index, subindex)); + if (entry_it == slave_it->second.pdo_entries.end()) { + return false; + } + const auto& entry = entry_it->second; + if (entry.rx || entry.cfg.bit_len != bit_len) { + return false; + } + value = maskValue_(entry.value, entry.cfg.bit_len); + return true; +} + +bool EthercatMotorBusRuntime::writeSdoRaw_(const int motor_id, + const std::uint16_t index, + const std::uint8_t subindex, + const std::uint8_t bit_len, + const std::uint64_t value) +{ + if (!isSupportedBitLength_(bit_len)) { + CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] unsupported SDO write bit length: " + << static_cast(bit_len) + << ", object=" << hexIndex_(index) + << ":" << static_cast(subindex) + << ", motor_id=" << motor_id; + return false; + } + + auto slave_it = slaves_by_motor_id_.find(motor_id); + if (slave_it == slaves_by_motor_id_.end()) { + CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] missing motor for SDO write: " + << motor_id << ", object=" << hexIndex_(index) + << ":" << static_cast(subindex); + return false; + } + auto& slave = slave_it->second; + if (!slave.slave_config || !master_) { + CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] runtime is not initialized for SDO write: " + << id_ << ", motor_id=" << motor_id + << ", object=" << hexIndex_(index) + << ":" << static_cast(subindex); + return false; + } + + const auto raw_value = maskValue_(value, bit_len); + if (!started_) { + int result = -1; + switch (bit_len) { + case 8: + result = ecrt_slave_config_sdo8(slave.slave_config, index, subindex, + static_cast(raw_value)); + break; + case 16: + result = ecrt_slave_config_sdo16(slave.slave_config, index, subindex, + static_cast(raw_value)); + break; + case 32: + result = ecrt_slave_config_sdo32(slave.slave_config, index, subindex, + static_cast(raw_value)); + break; + default: + break; + } + if (result != 0) { + CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] failed to configure startup SDO: " + << "group=" << id_ << ", motor_id=" << motor_id + << ", object=" << hexIndex_(index) + << ":" << static_cast(subindex) + << ", bit_len=" << static_cast(bit_len) + << ", value=" << raw_value + << ", result=" << result; + return false; + } + return true; + } + + std::array data{}; + switch (bit_len) { + case 8: + EC_WRITE_U8(data.data(), static_cast(raw_value)); + break; + case 16: + EC_WRITE_U16(data.data(), static_cast(raw_value)); + break; + case 32: + EC_WRITE_U32(data.data(), static_cast(raw_value)); + break; + default: + break; + } + + const auto data_size = static_cast(bit_len / 8); + std::uint32_t abort_code = 0; + const int result = ecrt_master_sdo_download( + master_, + static_cast(slave.cfg.position()), + index, + subindex, + data.data(), + data_size, + &abort_code); + if (result != 0) { + CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] failed to write SDO: " + << "group=" << id_ << ", motor_id=" << motor_id + << ", slave_position=" << slave.cfg.position() + << ", object=" << hexIndex_(index) + << ":" << static_cast(subindex) + << ", bit_len=" << static_cast(bit_len) + << ", value=" << raw_value + << ", result=" << result + << ", abort_code=" << hexIndex_(abort_code); + return false; + } + return true; +} + +bool EthercatMotorBusRuntime::readSdoRaw_(const int motor_id, + const std::uint16_t index, + const std::uint8_t subindex, + const std::uint8_t bit_len, + std::uint64_t& value) +{ + if (!isSupportedBitLength_(bit_len)) { + CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] unsupported SDO read bit length: " + << static_cast(bit_len) + << ", object=" << hexIndex_(index) + << ":" << static_cast(subindex) + << ", motor_id=" << motor_id; + return false; + } + + auto slave_it = slaves_by_motor_id_.find(motor_id); + if (slave_it == slaves_by_motor_id_.end()) { + CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] missing motor for SDO read: " + << motor_id << ", object=" << hexIndex_(index) + << ":" << static_cast(subindex); + return false; + } + const auto& slave = slave_it->second; + if (!master_) { + CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] runtime is not initialized for SDO read: " + << id_ << ", motor_id=" << motor_id + << ", object=" << hexIndex_(index) + << ":" << static_cast(subindex); + return false; + } + + std::array data{}; + const auto data_size = static_cast(bit_len / 8); + std::size_t result_size = 0; + std::uint32_t abort_code = 0; + const int result = ecrt_master_sdo_upload( + master_, + static_cast(slave.cfg.position()), + index, + subindex, + data.data(), + data_size, + &result_size, + &abort_code); + if (result != 0 || result_size != data_size) { + CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] failed to read SDO: " + << "group=" << id_ << ", motor_id=" << motor_id + << ", slave_position=" << slave.cfg.position() + << ", object=" << hexIndex_(index) + << ":" << static_cast(subindex) + << ", bit_len=" << static_cast(bit_len) + << ", result=" << result + << ", result_size=" << result_size + << ", abort_code=" << hexIndex_(abort_code); + return false; + } + + switch (bit_len) { + case 8: + value = EC_READ_U8(data.data()); + break; + case 16: + value = EC_READ_U16(data.data()); + break; + case 32: + value = EC_READ_U32(data.data()); + break; + default: + return false; + } + value = maskValue_(value, bit_len); + return true; +} + +std::uint32_t EthercatMotorBusRuntime::pdoEntryKey_(const std::uint16_t index, + const std::uint8_t subindex) +{ + return (static_cast(index) << 8U) | subindex; +} + +std::string EthercatMotorBusRuntime::hexIndex_(const std::uint32_t index) +{ + std::ostringstream oss; + oss << "0x" << std::hex << std::uppercase << index; + return oss.str(); +} + +std::uint64_t EthercatMotorBusRuntime::maskValue_(const std::uint64_t value, + const std::uint8_t bit_len) +{ + switch (bit_len) { + case 8: + return value & 0xFFU; + case 16: + return value & 0xFFFFU; + case 32: + return value & 0xFFFFFFFFULL; + default: + return value; + } +} + +bool EthercatMotorBusRuntime::isSupportedBitLength_(const std::uint8_t bit_len) +{ + return bit_len == 8 || bit_len == 16 || bit_len == 32; +} + +std::uint64_t EthercatMotorBusRuntime::readEntryValue_(const std::uint8_t* domain_data, + const PdoEntryRuntime& entry) +{ + switch (entry.cfg.bit_len) { + case 8: + return EC_READ_U8(domain_data + entry.offset); + case 16: + return EC_READ_U16(domain_data + entry.offset); + case 32: + return EC_READ_U32(domain_data + entry.offset); + default: + return 0; + } +} + +void EthercatMotorBusRuntime::writeEntryValue_(std::uint8_t* domain_data, + const PdoEntryRuntime& entry) +{ + switch (entry.cfg.bit_len) { + case 8: + EC_WRITE_U8(domain_data + entry.offset, static_cast(entry.value)); + break; + case 16: + EC_WRITE_U16(domain_data + entry.offset, static_cast(entry.value)); + break; + case 32: + EC_WRITE_U32(domain_data + entry.offset, static_cast(entry.value)); + break; + default: + break; + } } } // namespace cmvr::device diff --git a/cmvr-es/devices/motor/bus_runtime/ethercat/src/ethercat_motor_bus_runtime_real_test.cpp b/cmvr-es/devices/motor/bus_runtime/ethercat/src/ethercat_motor_bus_runtime_real_test.cpp new file mode 100644 index 00000000..0547d5cc --- /dev/null +++ b/cmvr-es/devices/motor/bus_runtime/ethercat/src/ethercat_motor_bus_runtime_real_test.cpp @@ -0,0 +1,91 @@ +#include "devices/motor/bus_runtime/ethercat/include/ethercat_motor_bus_runtime.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include "cmvr/msgs/cia402.pb.h" +#include "devices/motor/drivers/ethercat_motor/include/vendor/eyou/eyou_cia402_pdo_mapping.h" + +namespace cmvr::device { +namespace { + + + +config::MotorGroupConfig createSingleSlaveGroup() +{ + config::MotorGroupConfig group; + group.set_id("ethercat_real_test"); + group.set_bus_type(config::MOTOR_BUS_ETHERCAT); + group.set_vendor(config::MOTOR_VENDOR_EYOU); + group.set_protocol(config::MOTOR_PROTOCOL_ETHERCAT_CIA402); + + auto* ethercat = group.mutable_ethercat(); + ethercat->set_master_index(0); + ethercat->set_cycle_us(1000); + + auto* slave = ethercat->add_slaves(); + slave->set_motor_id(1); + slave->set_alias(0); + slave->set_position(0); + + return group; +} + +} // namespace + +TEST(EthercatMotorBusRuntimeRealTest, InitStartAndReadStatusword) +{ + + + EthercatMotorBusRuntime runtime; + runtime.setPdoMapping(createEyouCia402PdoMapping()); + + ASSERT_TRUE(runtime.init(createSingleSlaveGroup())); + EXPECT_EQ(runtime.busType(), config::MOTOR_BUS_ETHERCAT); + EXPECT_TRUE(runtime.hasMotor(1)); + EXPECT_NE(runtime.slaveForMotor(1), nullptr); + EXPECT_TRUE(runtime.hasPdoEntry(1, msgs::CIA402_CONTROL_WORD_6040, 0x00)); + EXPECT_TRUE(runtime.hasPdoEntry(1, msgs::CIA402_STATUS_WORD_6041, 0x00)); + EXPECT_TRUE(runtime.hasPdoEntry(1, msgs::CIA402_OPERATION_MODE_6060, 0x00)); + EXPECT_TRUE(runtime.hasPdoEntry(1, msgs::CIA402_MODE_DISPLAY_6061, 0x00)); + + const bool started = runtime.start(); + EXPECT_TRUE(started); + if (!started) { + runtime.stop(); + return; + } + + EXPECT_TRUE(runtime.writePdo(1, msgs::CIA402_CONTROL_WORD_6040, 0x00, 0x0000)); + EXPECT_TRUE(runtime.writePdo(1, msgs::CIA402_OPERATION_MODE_6060, 0x00, 0)); + + const int settle_ms = 1000; + std::this_thread::sleep_for(std::chrono::milliseconds(settle_ms)); + + std::uint16_t statusword = 0; + EXPECT_TRUE(runtime.readPdo(1, msgs::CIA402_STATUS_WORD_6041, 0x00, statusword)); + + std::int8_t mode_display = 0; + EXPECT_TRUE(runtime.readPdo(1, msgs::CIA402_MODE_DISPLAY_6061, 0x00, mode_display)); + + std::cout << "CIA402 statusword: 0x" << std::hex << statusword + << ", mode display: " << std::dec << static_cast(mode_display) + << std::endl; + + const int hold_ms = 10000; + if (hold_ms > 0) { + std::cout << "Holding EtherCAT runtime for " << hold_ms + << " ms. Check slave state in another terminal." << std::endl; + std::this_thread::sleep_for(std::chrono::milliseconds(hold_ms)); + } + + runtime.stop(); +} + +} // namespace cmvr::device diff --git a/dependency/x86/third_party/ethercat/v1.7.0/bin/ethercat b/dependency/x86/third_party/ethercat/v1.7.0/bin/ethercat new file mode 100755 index 00000000..50baa256 Binary files /dev/null and b/dependency/x86/third_party/ethercat/v1.7.0/bin/ethercat differ diff --git a/dependency/x86/third_party/ethercat/v1.7.0/etc/ethercat.conf b/dependency/x86/third_party/ethercat/v1.7.0/etc/ethercat.conf new file mode 100644 index 00000000..9ec7a2ce --- /dev/null +++ b/dependency/x86/third_party/ethercat/v1.7.0/etc/ethercat.conf @@ -0,0 +1,3 @@ +MASTER0_DEVICE="a0:ad:9f:c4:c2:2c" +DEVICE_MODULES="generic" +UPDOWN_INTERFACES="eno1" diff --git a/dependency/x86/third_party/ethercat/v1.7.0/etc/ethercat.conf.bak.20260702_140412 b/dependency/x86/third_party/ethercat/v1.7.0/etc/ethercat.conf.bak.20260702_140412 new file mode 100644 index 00000000..c1741260 --- /dev/null +++ b/dependency/x86/third_party/ethercat/v1.7.0/etc/ethercat.conf.bak.20260702_140412 @@ -0,0 +1,113 @@ +#------------------------------------------------------------------------------ +# +# EtherCAT master configuration file for use with ethercatctl. +# +# vim: spelllang=en spell tw=78 +# +#------------------------------------------------------------------------------ + +# +# Main Ethernet devices. +# +# The MASTER_DEVICE variable specifies the Ethernet device for a master +# with index 'X'. +# +# Specify the MAC address (hexadecimal with colons) of the Ethernet device to +# use. Example: "00:00:08:44:ab:66" +# +# Alternatively, a network interface name can be specified. The interface +# name will be resolved to a MAC address using the 'ip' command. +# Example: "eth0" +# +# The broadcast address "ff:ff:ff:ff:ff:ff" has a special meaning: It tells +# the master to accept the first device offered by any Ethernet driver. +# +# The MASTER_DEVICE variables also determine, how many masters will be +# created: A non-empty variable MASTER0_DEVICE will create one master, adding +# a non-empty variable MASTER1_DEVICE will create a second master, and so on. +# +# Examples: +# MASTER0_DEVICE="00:00:08:44:ab:66" +# MASTER0_DEVICE="eth0" +# +MASTER0_DEVICE="" +#MASTER1_DEVICE="" + +# +# Backup Ethernet devices +# +# The MASTER_BACKUP variables specify the devices used for redundancy. They +# behaves nearly the same as the MASTER_DEVICE variable, except that it +# does not interpret the ff:ff:ff:ff:ff:ff address. +# +#MASTER0_BACKUP="" + +# +# Ethernet driver modules to use for EtherCAT operation. +# +# Specify a non-empty list of Ethernet drivers, that shall be used for +# EtherCAT operation. +# +# Except for the generic Ethernet driver module, the init script will try to +# unload the usual Ethernet driver modules in the list and replace them with +# the EtherCAT-capable ones. If a certain (EtherCAT-capable) driver is not +# found, a warning will appear. +# +# Possible values: 8139too, e100, e1000, e1000e, r8169, generic, ccat, igb, +# igc, genet, dwmac-intel, stmmac-pci. +# Separate multiple drivers with spaces. +# A list of all matching kernel versions can be found here: +# https://docs.etherlab.org/ethercat/1.6/doxygen/devicedrivers.html +# +# Note: The e100, e1000, e1000e, r8169, ccat, igb and igc drivers are not +# built by default. Enable them with the --enable- configure switches. +# +DEVICE_MODULES="" + +# If you have any issues about network interfaces not being configured +# properly, systemd may need some additional infos about your setup. +# Have a look at the service file, you'll find some details there. +# + +# +# List of interfaces to bring up and down automatically. +# +# Specify a space-separated list of interface names (such as eth0 or +# enp0s1) that shall be brought up on `ethercatctl start` and down on +# `ethercatctl stop`. +# +# When using the generic driver, the corresponding Ethernet device has to be +# activated before the master is started, otherwise all frames will time out. +# This the perfect use-case for `UPDOWN_INTERFACES`. +# +UPDOWN_INTERFACES="" + +# +# Default SII caching method. +# +# Set the start-up caching method for all masters. The integer value +# determines, which fields are used to look up a cached SII page. It is a +# bit-field consisting of the following flags. A value of zero disables SII +# caching (default). A typical value is 7 (use vendor ID, product code and +# revision number for lookup). +# +# - 1: Vendor ID (always used) +# - 2: Product code (always used) +# - 4: Revision number +# - 8: Serial number +# - 16: Alias address +# +# Please keep in mind that in case serial number or alias address is enabled, +# only slaves with a non-zero serial number or alias benefit from caching. +# +SII_CACHING=0 + +# +# Flags for loading kernel modules. +# +# This can usually be left empty. Adjust this variable, if you have problems +# with module loading. +# +#MODPROBE_FLAGS="-b" + +#------------------------------------------------------------------------------ diff --git a/dependency/x86/third_party/ethercat/v1.7.0/etc/ethercat.conf.bak.20260702_155334 b/dependency/x86/third_party/ethercat/v1.7.0/etc/ethercat.conf.bak.20260702_155334 new file mode 100644 index 00000000..9ec7a2ce --- /dev/null +++ b/dependency/x86/third_party/ethercat/v1.7.0/etc/ethercat.conf.bak.20260702_155334 @@ -0,0 +1,3 @@ +MASTER0_DEVICE="a0:ad:9f:c4:c2:2c" +DEVICE_MODULES="generic" +UPDOWN_INTERFACES="eno1" diff --git a/dependency/x86/third_party/ethercat/v1.7.0/etc/init.d/ethercat b/dependency/x86/third_party/ethercat/v1.7.0/etc/init.d/ethercat new file mode 100755 index 00000000..63498a9f --- /dev/null +++ b/dependency/x86/third_party/ethercat/v1.7.0/etc/init.d/ethercat @@ -0,0 +1,123 @@ +#!/bin/sh + +#------------------------------------------------------------------------------ +# +# Init script for EtherCAT +# +# Copyright (C) 2006-2021 Florian Pose, Ingenieurgemeinschaft IgH +# +# This file is part of the IgH EtherCAT Master. +# +# The IgH EtherCAT Master is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 2, as +# published by the Free Software Foundation. +# +# The IgH EtherCAT Master is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with the IgH EtherCAT Master; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# +# +# vim: expandtab +# +#------------------------------------------------------------------------------ + +### BEGIN INIT INFO +# Provides: ethercat +# Required-Start: $local_fs $syslog $network +# Should-Start: $time ntp +# Required-Stop: $local_fs $syslog $network +# Should-Stop: $time ntp +# Default-Start: 3 5 +# Default-Stop: 0 1 2 6 +# Short-Description: EtherCAT master +# Description: EtherCAT master 1.7.0 +### END INIT INFO + +#------------------------------------------------------------------------------ + +ETHERCATCTL="/home/lgv/cmvr/0-workspace/cmvr-es/dependency/x86/third_party/ethercat/v1.7.0/sbin/ethercatctl -c /home/lgv/cmvr/0-workspace/cmvr-es/dependency/x86/third_party/ethercat/v1.7.0/etc/sysconfig/ethercat" + +#------------------------------------------------------------------------------ + +exit_success() { + if [ -r /etc/rc.status ]; then + rc_reset + rc_status -v + rc_exit + else + echo " done" + exit 0 + fi +} + +#------------------------------------------------------------------------------ + +exit_fail() { + if [ -r /etc/rc.status ]; then + rc_failed + rc_status -v + rc_exit + else + echo " failed" + exit 1 + fi +} + +#------------------------------------------------------------------------------ + +if [ -r /etc/rc.status ]; then + . /etc/rc.status + rc_reset +fi + +case "${1}" in + +start) + echo -n "Starting EtherCAT master 1.7.0 " + + if $ETHERCATCTL start; then + exit_success + else + exit_fail + fi + ;; + +stop) + echo -n "Shutting down EtherCAT master 1.7.0 " + + if $ETHERCATCTL stop; then + exit_success + else + exit_fail + fi + ;; + +restart) + $0 stop || exit 1 + sleep 1 + $0 start + ;; + +status) + $ETHERCATCTL status + exit $? + ;; + +*) + echo "USAGE: $0 {start|stop|restart|status}" + ;; + +esac + +if [ -r /etc/rc.status ]; then + rc_exit +else + exit 1 +fi + +#------------------------------------------------------------------------------ diff --git a/dependency/x86/third_party/ethercat/v1.7.0/etc/sysconfig/ethercat b/dependency/x86/third_party/ethercat/v1.7.0/etc/sysconfig/ethercat new file mode 100644 index 00000000..4bf8ea93 --- /dev/null +++ b/dependency/x86/third_party/ethercat/v1.7.0/etc/sysconfig/ethercat @@ -0,0 +1,113 @@ +#------------------------------------------------------------------------------ +# +# EtherCAT master configuration file for use with init.d. +# +# vim: spelllang=en spell tw=78 +# +#------------------------------------------------------------------------------ + +# +# Main Ethernet devices. +# +# The MASTER_DEVICE variable specifies the Ethernet device for a master +# with index 'X'. +# +# Specify the MAC address (hexadecimal with colons) of the Ethernet device to +# use. Example: "00:00:08:44:ab:66" +# +# Alternatively, a network interface name can be specified. The interface +# name will be resolved to a MAC address using the 'ip' command. +# Example: "eth0" +# +# The broadcast address "ff:ff:ff:ff:ff:ff" has a special meaning: It tells +# the master to accept the first device offered by any Ethernet driver. +# +# The MASTER_DEVICE variables also determine, how many masters will be +# created: A non-empty variable MASTER0_DEVICE will create one master, adding +# a non-empty variable MASTER1_DEVICE will create a second master, and so on. +# +# Examples: +# MASTER0_DEVICE="00:00:08:44:ab:66" +# MASTER0_DEVICE="eth0" +# +MASTER0_DEVICE="" +#MASTER1_DEVICE="" + +# +# Backup Ethernet devices +# +# The MASTER_BACKUP variables specify the devices used for redundancy. They +# behaves nearly the same as the MASTER_DEVICE variable, except that it +# does not interpret the ff:ff:ff:ff:ff:ff address. +# +#MASTER0_BACKUP="" + +# +# Ethernet driver modules to use for EtherCAT operation. +# +# Specify a non-empty list of Ethernet drivers, that shall be used for +# EtherCAT operation. +# +# Except for the generic Ethernet driver module, the init script will try to +# unload the usual Ethernet driver modules in the list and replace them with +# the EtherCAT-capable ones. If a certain (EtherCAT-capable) driver is not +# found, a warning will appear. +# +# Possible values: 8139too, e100, e1000, e1000e, r8169, generic, ccat, igb, +# igc, genet, dwmac-intel, stmmac-pci. +# Separate multiple drivers with spaces. +# A list of all matching kernel versions can be found here: +# https://docs.etherlab.org/ethercat/1.6/doxygen/devicedrivers.html +# +# Note: The e100, e1000, e1000e, r8169, ccat, igb and igc drivers are not +# built by default. Enable them with the --enable- configure switches. +# +DEVICE_MODULES="" + +# If you have any issues about network interfaces not being configured +# properly, systemd may need some additional infos about your setup. +# Have a look at the service file, you'll find some details there. +# + +# +# List of interfaces to bring up and down automatically. +# +# Specify a space-separated list of interface names (such as eth0 or +# enp0s1) that shall be brought up on `ethercatctl start` and down on +# `ethercatctl stop`. +# +# When using the generic driver, the corresponding Ethernet device has to be +# activated before the master is started, otherwise all frames will time out. +# This the perfect use-case for `UPDOWN_INTERFACES`. +# +UPDOWN_INTERFACES="" + +# +# Default SII caching method. +# +# Set the start-up caching method for all masters. The integer value +# determines, which fields are used to look up a cached SII page. It is a +# bit-field consisting of the following flags. A value of zero disables SII +# caching (default). A typical value is 7 (use vendor ID, product code and +# revision number for lookup). +# +# - 1: Vendor ID (always used) +# - 2: Product code (always used) +# - 4: Revision number +# - 8: Serial number +# - 16: Alias address +# +# Please keep in mind that in case serial number or alias address is enabled, +# only slaves with a non-zero serial number or alias benefit from caching. +# +SII_CACHING=0 + +# +# Flags for loading kernel modules. +# +# This can usually be left empty. Adjust this variable, if you have problems +# with module loading. +# +#MODPROBE_FLAGS="-b" + +#------------------------------------------------------------------------------ diff --git a/dependency/x86/third_party/ethercat/v1.7.0/include/ecrt.h b/dependency/x86/third_party/ethercat/v1.7.0/include/ecrt.h new file mode 100644 index 00000000..64a43ec3 --- /dev/null +++ b/dependency/x86/third_party/ethercat/v1.7.0/include/ecrt.h @@ -0,0 +1,3216 @@ +/***************************************************************************** + * + * Copyright (C) 2006-2026 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT master userspace library. + * + * The IgH EtherCAT master userspace library is free software; you can + * redistribute it and/or modify it under the terms of the GNU Lesser General + * Public License as published by the Free Software Foundation; version 2.1 + * of the License. + * + * The IgH EtherCAT master userspace library is distributed in the hope that + * it will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the IgH EtherCAT master userspace library. If not, see + * . + * + ****************************************************************************/ + +/** \file + * + * EtherCAT master application interface. + * + * \defgroup ApplicationInterface EtherCAT Application Interface + * + * EtherCAT interface for realtime applications. This interface is designed + * for realtime modules that want to use EtherCAT. There are functions to + * request a master, to map process data, to communicate with slaves via CoE + * and to configure and activate the bus. + * + * Changes in version 1.7.0: + * + * - Added ecrt_master_sii_caching() to set the SII caching method and added + * the feature flag EC_HAVE_SII_CACHING and the enum type + * ec_sii_caching_fields_t. + * + * Changes in version 1.6.0: + * + * - Added the ecrt_master_scan_progress() method, the + * ec_master_scan_progress_t structure and the EC_HAVE_SCAN_PROGRESS + * definition to check for its existence. + * - Added the EoE configuration methods ecrt_slave_config_eoe_mac_address(), + * ecrt_slave_config_eoe_ip_address(), ecrt_slave_config_eoe_subnet_mask(), + * ecrt_slave_config_eoe_default_gateway(), + * ecrt_slave_config_eoe_dns_address(), + * ecrt_slave_config_eoe_hostname() and the EC_HAVE_SET_IP + * definition to check for its existence. + * - Added ecrt_slave_config_state_timeout() to set the application-layer + * state change timeout and EC_HAVE_STATE_TIMEOUT to check for its + * existence. + * + * Changes since version 1.5.2: + * + * - Added the ecrt_slave_config_flag() method and the EC_HAVE_FLAGS + * definition to check for its existence. + * - Added SoE IDN requests, including the datatype ec_soe_request_t and the + * methods ecrt_slave_config_create_soe_request(), + * ecrt_soe_request_object(), ecrt_soe_request_timeout(), + * ecrt_soe_request_data(), ecrt_soe_request_data_size(), + * ecrt_soe_request_state(), ecrt_soe_request_write() and + * ecrt_soe_request_read(). Use the EC_HAVE_SOE_REQUESTS to check, if the + * functionality is available. + * + * Changes in version 1.5.2: + * + * - Added redundancy_active flag to ec_domain_state_t. + * - Added ecrt_master_link_state() method and ec_master_link_state_t to query + * the state of a redundant link. + * - Added the EC_HAVE_REDUNDANCY define, to check, if the interface contains + * redundancy features. + * - Added ecrt_sdo_request_index() to change SDO index and subindex after + * request creation. + * - Added interface for retrieving CoE emergency messages, i. e. + * ecrt_slave_config_emerg_size(), ecrt_slave_config_emerg_pop(), + * ecrt_slave_config_emerg_clear(), ecrt_slave_config_emerg_overruns() and + * the defines EC_HAVE_EMERGENCY and EC_COE_EMERGENCY_MSG_SIZE. + * - Added interface for direct EtherCAT register access: Added data type + * ec_reg_request_t and methods ecrt_slave_config_create_reg_request(), + * ecrt_reg_request_data(), ecrt_reg_request_state(), + * ecrt_reg_request_write(), ecrt_reg_request_read() and the feature flag + * EC_HAVE_REG_ACCESS. + * - Added method to select the reference clock, + * ecrt_master_select_reference_clock() and the feature flag + * EC_HAVE_SELECT_REF_CLOCK to check, if the method is available. + * - Added method to get the reference clock time, + * ecrt_master_reference_clock_time() and the feature flag + * EC_HAVE_REF_CLOCK_TIME to have the possibility to synchronize the master + * clock to the reference clock. + * - Changed the data types of the shift times in ecrt_slave_config_dc() to + * int32_t to correctly display negative shift times. + * - Added ecrt_slave_config_reg_pdo_entry_pos() and the feature flag + * EC_HAVE_REG_BY_POS for registering PDO entries with non-unique indices + * via their positions in the mapping. + * + * Changes in version 1.5: + * + * - Added the distributed clocks feature and the respective method + * ecrt_slave_config_dc() to configure a slave for cyclic operation, and + * ecrt_master_application_time(), ecrt_master_sync_reference_clock() and + * ecrt_master_sync_slave_clocks() for offset and drift compensation. The + * EC_TIMEVAL2NANO() macro can be used for epoch time conversion, while the + * ecrt_master_sync_monitor_queue() and ecrt_master_sync_monitor_process() + * methods can be used to monitor the synchrony. + * - Improved the callback mechanism. ecrt_master_callbacks() now takes two + * callback functions for sending and receiving datagrams. + * ecrt_master_send_ext() is used to execute the sending of non-application + * datagrams. + * - Added watchdog configuration (method ecrt_slave_config_watchdog(), + * #ec_watchdog_mode_t, \a watchdog_mode parameter in ec_sync_info_t and + * ecrt_slave_config_sync_manager()). + * - Added ecrt_slave_config_complete_sdo() method to download an SDO during + * configuration via CompleteAccess. + * - Added ecrt_master_deactivate() to remove the master configuration. + * - Added ecrt_open_master() and ecrt_master_reserve() separation for + * userspace. + * - Added master information interface (methods ecrt_master(), + * ecrt_master_get_slave(), ecrt_master_get_sync_manager(), + * ecrt_master_get_pdo() and ecrt_master_get_pdo_entry()) to get information + * about the currently connected slaves and the PDO entries provided. + * - Added ecrt_master_sdo_download(), ecrt_master_sdo_download_complete() and + * ecrt_master_sdo_upload() methods to let an application transfer SDOs + * before activating the master. + * - Changed the meaning of the negative return values of + * ecrt_slave_config_reg_pdo_entry() and ecrt_slave_config_sdo*(). + * - Implemented the Vendor-specific over EtherCAT mailbox protocol. See + * ecrt_slave_config_create_voe_handler(). + * - Renamed ec_sdo_request_state_t to #ec_request_state_t, because it is also + * used by VoE handlers. + * - Removed 'const' from argument of ecrt_sdo_request_state(), because the + * userspace library has to modify object internals. + * - Added 64-bit data access macros. + * - Added ecrt_slave_config_idn() method for storing SoE IDN configurations, + * and ecrt_master_read_idn() and ecrt_master_write_idn() to read/write IDNs + * ad-hoc via the user-space library. + * - Added ecrt_master_reset() to initiate retrying to configure slaves. + * + * @{ + */ + +/****************************************************************************/ + +#ifndef __ECRT_H__ +#define __ECRT_H__ + +#ifdef __KERNEL__ +#include +#include +#include +#include // struct in_addr +#else +#include // for size_t +#include +#include // for struct timeval +#include // struct in_addr +#endif + +/***************************************************************************** + * Global definitions + ****************************************************************************/ + +/** EtherCAT realtime interface major version number. + */ +#define ECRT_VER_MAJOR 1 + +/** EtherCAT realtime interface minor version number. + */ +#define ECRT_VER_MINOR 6 + +/** EtherCAT realtime interface version word generator. + */ +#define ECRT_VERSION(a, b) (((a) << 8) + (b)) + +/** EtherCAT realtime interface version word. + */ +#define ECRT_VERSION_MAGIC ECRT_VERSION(ECRT_VER_MAJOR, ECRT_VER_MINOR) + +/***************************************************************************** + * Feature flags + ****************************************************************************/ + +/** Defined, if the redundancy features are available. + * + * I. e. if the \a redundancy_active flag in ec_domain_state_t and the + * ecrt_master_link_state() method are available. + */ +#define EC_HAVE_REDUNDANCY + +/** Defined, if the CoE emergency ring feature is available. + * + * I. e. if the ecrt_slave_config_emerg_*() methods are available. + */ +#define EC_HAVE_EMERGENCY + +/** Defined, if the register access interface is available. + * + * I. e. if the methods ecrt_slave_config_create_reg_request(), + * ecrt_reg_request_data(), ecrt_reg_request_state(), ecrt_reg_request_write() + * and ecrt_reg_request_read() are available. + */ +#define EC_HAVE_REG_ACCESS + +/** Defined if the method ecrt_master_select_reference_clock() is available. + */ +#define EC_HAVE_SELECT_REF_CLOCK + +/** Defined if the method ecrt_master_reference_clock_time() is available. + */ +#define EC_HAVE_REF_CLOCK_TIME + +/** Defined if the method ecrt_slave_config_reg_pdo_entry_pos() is available. + */ +#define EC_HAVE_REG_BY_POS + +/** Defined if the method ecrt_master_sync_reference_clock_to() is available. + */ +#define EC_HAVE_SYNC_TO + +/** Defined if the method ecrt_slave_config_flag() is available. + */ +#define EC_HAVE_FLAGS + +/** Defined if the methods ecrt_slave_config_create_soe_request(), + * ecrt_soe_request_object(), ecrt_soe_request_timeout(), + * ecrt_soe_request_data(), ecrt_soe_request_data_size(), + * ecrt_soe_request_state(), ecrt_soe_request_write() and + * ecrt_soe_request_read() and the datatype ec_soe_request_t are available. + */ +#define EC_HAVE_SOE_REQUESTS + +/** Defined, if the method ecrt_master_scan_progress() and the + * ec_master_scan_progress_t structure are available. + */ +#define EC_HAVE_SCAN_PROGRESS + +/** Defined, if the methods ecrt_slave_config_eoe_mac_address(), + * ecrt_slave_config_eoe_ip_address(), ecrt_slave_config_eoe_subnet_mask(), + * ecrt_slave_config_eoe_default_gateway(), + * ecrt_slave_config_eoe_dns_address(), ecrt_slave_config_eoe_hostname() are + * available. + */ +#define EC_HAVE_SET_IP + +/** Defined, if the method ecrt_slave_config_state_timeout() is available. + */ +#define EC_HAVE_STATE_TIMEOUT + +/** Defined, if the method ecrt_master_sii_caching() and the enum type + * ec_sii_caching_fields_t and its values are available. + */ +#define EC_HAVE_SII_CACHING + +/****************************************************************************/ + +/** Symbol visibility control macro. + */ +#ifndef EC_PUBLIC_API +# if defined(ethercat_EXPORTS) && !defined(__KERNEL__) +# define EC_PUBLIC_API __attribute__ ((visibility ("default"))) +# else +# define EC_PUBLIC_API +# endif +#endif + +/****************************************************************************/ + +/** End of list marker. + * + * This can be used with ecrt_slave_config_pdos(). + */ +#define EC_END ~0U + +/** Maximum number of sync managers per slave. + */ +#define EC_MAX_SYNC_MANAGERS 16 + +/** Maximum string length. + * + * Used in ec_slave_info_t. + */ +#define EC_MAX_STRING_LENGTH 64 + +/** Maximum number of slave ports. */ +#define EC_MAX_PORTS 4 + +/** Timeval to nanoseconds conversion. + * + * This macro converts a Unix epoch time to EtherCAT DC time. + * + * \see void ecrt_master_application_time() + * + * \param TV struct timeval containing epoch time. + */ +#define EC_TIMEVAL2NANO(TV) \ + (((TV).tv_sec - 946684800ULL) * 1000000000ULL + (TV).tv_usec * 1000ULL) + +/** Size of a CoE emergency message in byte. + * + * \see ecrt_slave_config_emerg_pop(). + */ +#define EC_COE_EMERGENCY_MSG_SIZE 8 + +/***************************************************************************** + * Data types + ****************************************************************************/ + +struct ec_master; +typedef struct ec_master ec_master_t; /**< \see ec_master */ + +struct ec_slave_config; +typedef struct ec_slave_config ec_slave_config_t; /**< \see ec_slave_config */ + +struct ec_domain; +typedef struct ec_domain ec_domain_t; /**< \see ec_domain */ + +struct ec_sdo_request; +typedef struct ec_sdo_request ec_sdo_request_t; /**< \see ec_sdo_request. */ + +struct ec_soe_request; +typedef struct ec_soe_request ec_soe_request_t; /**< \see ec_soe_request. */ + +struct ec_voe_handler; +typedef struct ec_voe_handler ec_voe_handler_t; /**< \see ec_voe_handler. */ + +struct ec_reg_request; +typedef struct ec_reg_request ec_reg_request_t; /**< \see ec_reg_request. */ + +/****************************************************************************/ + +/** Master state. + * + * This is used for the output parameter of ecrt_master_state(). + * + * \see ecrt_master_state(). + */ +typedef struct { + unsigned int slaves_responding; /**< Sum of responding slaves on all + Ethernet devices. */ + unsigned int al_states : 4; /**< Application-layer states of all slaves. + The states are coded in the lower 4 bits. + If a bit is set, it means that at least one + slave in the network is in the corresponding + state: + - Bit 0: \a INIT + - Bit 1: \a PREOP + - Bit 2: \a SAFEOP + - Bit 3: \a OP */ + unsigned int link_up : 1; /**< \a true, if at least one Ethernet link is + up. */ +} ec_master_state_t; + +/****************************************************************************/ + +/** Redundant link state. + * + * This is used for the output parameter of ecrt_master_link_state(). + * + * \see ecrt_master_link_state(). + */ +typedef struct { + unsigned int slaves_responding; /**< Sum of responding slaves on the given + link. */ + unsigned int al_states : 4; /**< Application-layer states of the slaves on + the given link. The states are coded in the + lower 4 bits. If a bit is set, it means + that at least one slave in the network is in + the corresponding state: + - Bit 0: \a INIT + - Bit 1: \a PREOP + - Bit 2: \a SAFEOP + - Bit 3: \a OP */ + unsigned int link_up : 1; /**< \a true, if the given Ethernet link is up. + */ +} ec_master_link_state_t; + +/****************************************************************************/ + +/** Slave configuration state. + * + * This is used as an output parameter of ecrt_slave_config_state(). + * + * \see ecrt_slave_config_state(). + */ +typedef struct { + unsigned int online : 1; /**< The slave is online. */ + unsigned int operational : 1; /**< The slave was brought into \a OP state + using the specified configuration. */ + unsigned int al_state : 4; /**< The application-layer state of the slave. + - 1: \a INIT + - 2: \a PREOP + - 4: \a SAFEOP + - 8: \a OP + + Note that each state is coded in a different + bit! */ +} ec_slave_config_state_t; + +/****************************************************************************/ + +/** Master information. + * + * This is used as an output parameter of ecrt_master(). + * + * \see ecrt_master(). + */ +typedef struct { + unsigned int slave_count; /**< Number of slaves in the network. */ + unsigned int link_up : 1; /**< \a true, if the network link is up. */ + uint8_t scan_busy; /**< \a true, while the master is scanning the network. + */ + uint64_t app_time; /**< Application time. */ +} ec_master_info_t; + +/****************************************************************************/ + +/** Master scan progress information. + * + * This is used as an output parameter of ecrt_master_scan_progress(). + * + * \see ecrt_master_scan_progress(). + */ +typedef struct { + unsigned int slave_count; /**< Number of slaves detected. */ + unsigned int scan_index; /**< Index of the slave that is currently + scanned. If it is less than the \a + slave_count, the network scan is in progress. + */ +} ec_master_scan_progress_t; + +/****************************************************************************/ + +/** EtherCAT slave port descriptor. + */ +typedef enum { + EC_PORT_NOT_IMPLEMENTED, /**< Port is not implemented. */ + EC_PORT_NOT_CONFIGURED, /**< Port is not configured. */ + EC_PORT_EBUS, /**< Port is an E-Bus. */ + EC_PORT_MII /**< Port is a MII. */ +} ec_slave_port_desc_t; + +/****************************************************************************/ + +/** EtherCAT slave port information. + */ +typedef struct { + uint8_t link_up; /**< Link detected. */ + uint8_t loop_closed; /**< Loop closed. */ + uint8_t signal_detected; /**< Detected signal on RX port. */ +} ec_slave_port_link_t; + +/****************************************************************************/ + +/** Slave information. + * + * This is used as an output parameter of ecrt_master_get_slave(). + * + * \see ecrt_master_get_slave(). + */ +typedef struct { + uint16_t position; /**< Offset of the slave in the ring. */ + uint32_t vendor_id; /**< Vendor-ID stored on the slave. */ + uint32_t product_code; /**< Product-Code stored on the slave. */ + uint32_t revision_number; /**< Revision-Number stored on the slave. */ + uint32_t serial_number; /**< Serial-Number stored on the slave. */ + uint16_t alias; /**< The slaves alias if not equal to 0. */ + int16_t current_on_ebus; /**< Used current in mA. */ + struct { + ec_slave_port_desc_t desc; /**< Physical port type. */ + ec_slave_port_link_t link; /**< Port link state. */ + uint32_t receive_time; /**< Receive time on DC transmission delay + measurement. */ + uint16_t next_slave; /**< Ring position of next DC slave on that + port. */ + uint32_t delay_to_next_dc; /**< Delay [ns] to next DC slave. */ + } ports[EC_MAX_PORTS]; /**< Port information. */ + uint8_t al_state; /**< Current state of the slave. */ + uint8_t error_flag; /**< Error flag for that slave. */ + uint8_t sync_count; /**< Number of sync managers. */ + uint16_t sdo_count; /**< Number of SDOs. */ + char name[EC_MAX_STRING_LENGTH]; /**< Name of the slave. */ +} ec_slave_info_t; + +/****************************************************************************/ + +/** Domain working counter interpretation. + * + * This is used in ec_domain_state_t. + */ +typedef enum { + EC_WC_ZERO = 0, /**< No registered process data were exchanged. */ + EC_WC_INCOMPLETE, /**< Some of the registered process data were + exchanged. */ + EC_WC_COMPLETE /**< All registered process data were exchanged. */ +} ec_wc_state_t; + +/****************************************************************************/ + +/** Domain state. + * + * This is used for the output parameter of ecrt_domain_state(). + */ +typedef struct { + unsigned int working_counter; /**< Value of the last working counter. */ + ec_wc_state_t wc_state; /**< Working counter interpretation. */ + unsigned int redundancy_active; /**< Redundant link is in use. */ +} ec_domain_state_t; + +/****************************************************************************/ + +/** Direction type for PDO assignment functions. + */ +typedef enum { + EC_DIR_INVALID, /**< Invalid direction. Do not use this value. */ + EC_DIR_OUTPUT, /**< Values written by the master. */ + EC_DIR_INPUT, /**< Values read by the master. */ + EC_DIR_COUNT /**< Number of directions. For internal use only. */ +} ec_direction_t; + +/****************************************************************************/ + +/** Watchdog mode for sync manager configuration. + * + * Used to specify, if a sync manager's watchdog is to be enabled. + */ +typedef enum { + EC_WD_DEFAULT, /**< Use the default setting of the sync manager. */ + EC_WD_ENABLE, /**< Enable the watchdog. */ + EC_WD_DISABLE, /**< Disable the watchdog. */ +} ec_watchdog_mode_t; + +/****************************************************************************/ + +/** PDO entry configuration information. + * + * This is the data type of the \a entries field in ec_pdo_info_t. + * + * \see ecrt_slave_config_pdos(). + */ +typedef struct { + uint16_t index; /**< PDO entry index. */ + uint8_t subindex; /**< PDO entry subindex. */ + uint8_t bit_length; /**< Size of the PDO entry in bit. */ +} ec_pdo_entry_info_t; + +/****************************************************************************/ + +/** PDO configuration information. + * + * This is the data type of the \a pdos field in ec_sync_info_t. + * + * \see ecrt_slave_config_pdos(). + */ +typedef struct { + uint16_t index; /**< PDO index. */ + unsigned int n_entries; /**< Number of PDO entries in \a entries to map. + Zero means, that the default mapping shall be + used (this can only be done if the slave is + present at configuration time). */ + ec_pdo_entry_info_t const *entries; /**< Array of PDO entries to map. Can + either be \a NULL, or must contain + at least \a n_entries values. */ +} ec_pdo_info_t; + +/****************************************************************************/ + +/** Sync manager configuration information. + * + * This can be use to configure multiple sync managers including the PDO + * assignment and PDO mapping. It is used as an input parameter type in + * ecrt_slave_config_pdos(). + */ +typedef struct { + uint8_t index; /**< Sync manager index. Must be less + than #EC_MAX_SYNC_MANAGERS for a valid sync manager, + but can also be \a 0xff to mark the end of the list. */ + ec_direction_t dir; /**< Sync manager direction. */ + unsigned int n_pdos; /**< Number of PDOs in \a pdos. */ + ec_pdo_info_t const *pdos; /**< Array with PDOs to assign. This must + contain at least \a n_pdos PDOs. */ + ec_watchdog_mode_t watchdog_mode; /**< Watchdog mode. */ +} ec_sync_info_t; + +/****************************************************************************/ + +/** List record type for PDO entry mass-registration. + * + * This type is used for the array parameter of the + * ecrt_domain_reg_pdo_entry_list() + */ +typedef struct { + uint16_t alias; /**< Slave alias address. */ + uint16_t position; /**< Slave position. */ + uint32_t vendor_id; /**< Slave vendor ID. */ + uint32_t product_code; /**< Slave product code. */ + uint16_t index; /**< PDO entry index. */ + uint8_t subindex; /**< PDO entry subindex. */ + unsigned int *offset; /**< Pointer to a variable to store the PDO entry's + (byte-)offset in the process data. */ + unsigned int *bit_position; /**< Pointer to a variable to store a bit + position (0-7) within the \a offset. Can be + NULL, in which case an error is raised if + the PDO entry does not byte-align. */ +} ec_pdo_entry_reg_t; + +/****************************************************************************/ + +/** Request state. + * + * This is used as return type for ecrt_sdo_request_state() and + * ecrt_voe_handler_state(). + */ +typedef enum { + EC_REQUEST_UNUSED, /**< Not requested. */ + EC_REQUEST_BUSY, /**< Request is being processed. */ + EC_REQUEST_SUCCESS, /**< Request was processed successfully. */ + EC_REQUEST_ERROR, /**< Request processing failed. */ +} ec_request_state_t; + +/****************************************************************************/ + +/** Application-layer state. + */ +typedef enum { + EC_AL_STATE_INIT = 1, /**< Init. */ + EC_AL_STATE_PREOP = 2, /**< Pre-operational. */ + EC_AL_STATE_SAFEOP = 4, /**< Safe-operational. */ + EC_AL_STATE_OP = 8, /**< Operational. */ +} ec_al_state_t; + +/****************************************************************************/ + +/** Fields for SII caching. + * + * For use in the method ecrt_master_sii_caching(). + */ +typedef enum { + EC_SII_DISABLE_CACHING = 0, /** Disable SII caching. */ + EC_SII_VENDOR = 1, /** Use vendor ID. */ + EC_SII_PRODUCT = 2, /** Use product code. */ + EC_SII_REVISION = 4, /** Use revision number. */ + EC_SII_SERIAL = 8, /** Use serial number. */ + EC_SII_ALIAS = 16, /** Use alias address. */ +} ec_sii_caching_fields_t; + +/***************************************************************************** + * Global functions + ****************************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +/** Returns the version magic of the realtime interface. + * + * \apiusage{master_any,rt_safe} + * + * \return Value of ECRT_VERSION_MAGIC() at EtherCAT master compile time. + */ +EC_PUBLIC_API unsigned int ecrt_version_magic(void); + +/** Requests an EtherCAT master for realtime operation. + * + * Before an application can access an EtherCAT master, it has to reserve one + * for exclusive use. + * + * In userspace, this is a convenience function for ecrt_open_master() and + * ecrt_master_reserve(). + * + * This function has to be the first function an application has to call to + * use EtherCAT. The function takes the index of the master as its argument. + * The first master has index 0, the n-th master has index n - 1. The number + * of masters has to be specified when loading the master module. + * + * \apiusage{master_idle,blocking} + * + * \return Pointer to the reserved master, otherwise \a NULL. + */ +EC_PUBLIC_API ec_master_t *ecrt_request_master( + unsigned int master_index /**< Index of the master to request. */ + ); + +#ifndef __KERNEL__ + +/** Opens an EtherCAT master for userspace access. + * + * This function has to be the first function an application has to call to + * use EtherCAT. The function takes the index of the master as its argument. + * The first master has index 0, the n-th master has index n - 1. The number + * of masters has to be specified when loading the master module. + * + * For convenience, the function ecrt_request_master() can be used. + * + * \apiusage{master_idle,blocking} + * + * \return Pointer to the opened master, otherwise \a NULL. + */ +EC_PUBLIC_API ec_master_t *ecrt_open_master( + unsigned int master_index /**< Index of the master to request. */ + ); + +#endif // #ifndef __KERNEL__ + +/** Releases a requested EtherCAT master. + * + * After use, a master it has to be released to make it available for other + * applications. + * + * This method frees all created data structures. It should not be called in + * realtime context. + * + * If the master was activated, ecrt_master_deactivate() is called internally. + * + * \apiusage{master_any,blocking} + */ +EC_PUBLIC_API void ecrt_release_master( + ec_master_t *master /**< EtherCAT master */ + ); + +/***************************************************************************** + * Master methods + ****************************************************************************/ + +#ifndef __KERNEL__ + +/** Reserves an EtherCAT master for realtime operation. + * + * Before an application can use PDO/domain registration functions or SDO + * request functions on the master, it has to reserve one for exclusive use. + * + * \apiusage{master_idle,blocking} + * + * \return 0 in case of success, else < 0 + */ +EC_PUBLIC_API int ecrt_master_reserve( + ec_master_t *master /**< EtherCAT master */ + ); + +#endif // #ifndef __KERNEL__ + +#ifdef __KERNEL__ + +/** Sets the locking callbacks. + * + * For concurrent master access, i. e. if other instances than the application + * want to send and receive datagrams on the network, the application has to + * provide a callback mechanism. This method takes two function pointers as + * its parameters. Asynchronous master access (like EoE processing) is only + * possible if the callbacks have been set. + * + * The task of the send callback (\a send_cb) is to decide, if the network + * hardware is currently accessible and whether or not to call the + * ecrt_master_send_ext() method. + * + * The task of the receive callback (\a receive_cb) is to decide, if a call to + * ecrt_master_receive() is allowed and to execute it respectively. + * + * \apiusage{master_idle,blocking} + * + * \attention This method has to be called before ecrt_master_activate(). + */ +void ecrt_master_callbacks( + ec_master_t *master, /**< EtherCAT master */ + void (*send_cb)(void *), /**< Datagram sending callback. */ + void (*receive_cb)(void *), /**< Receive callback. */ + void *cb_data /**< Arbitrary pointer passed to the callback functions. + */ + ); + +#endif /* __KERNEL__ */ + +/** Creates a new process data domain. + * + * For process data exchange, at least one process data domain is needed. + * This method creates a new process data domain and returns a pointer to the + * new domain object. This object can be used for registering PDOs and + * exchanging them in cyclic operation. + * + * This method allocates memory and should be called in non-realtime context + * before ecrt_master_activate(). + * + * \apiusage{master_idle,blocking} + * + * \return Pointer to the new domain on success, else NULL. + */ +EC_PUBLIC_API ec_domain_t *ecrt_master_create_domain( + ec_master_t *master /**< EtherCAT master. */ + ); + +/** Obtains a slave configuration. + * + * Creates a slave configuration object for the given \a alias and \a position + * tuple and returns it. If a configuration with the same \a alias and \a + * position already exists, it will be re-used. In the latter case, the given + * vendor ID and product code are compared to the stored ones. On mismatch, an + * error message is raised and the function returns \a NULL. + * + * Slaves are addressed with the \a alias and \a position parameters. + * - If \a alias is zero, \a position is interpreted as the desired slave's + * ring position. + * - If \a alias is non-zero, it matches a slave with the given alias. In this + * case, \a position is interpreted as ring offset, starting from the + * aliased slave, so a position of zero means the aliased slave itself and a + * positive value matches the n-th slave behind the aliased one. + * + * If the slave with the given address is found during the configuration, + * its vendor ID and product code are matched against the given value. On + * mismatch, the slave is not configured and an error message is raised. + * + * If different slave configurations are pointing to the same slave during + * configuration, a warning is raised and only the first configuration is + * applied. + * + * This method allocates memory and should be called in non-realtime context + * before ecrt_master_activate(). + * + * \apiusage{master_idle,blocking} + * + * \retval >0 Pointer to the slave configuration structure. + * \retval NULL in the error case. + */ +EC_PUBLIC_API ec_slave_config_t *ecrt_master_slave_config( + ec_master_t *master, /**< EtherCAT master */ + uint16_t alias, /**< Slave alias. */ + uint16_t position, /**< Slave position. */ + uint32_t vendor_id, /**< Expected vendor ID. */ + uint32_t product_code /**< Expected product code. */ + ); + +/** Selects the reference clock for distributed clocks. + * + * If this method is not called for a certain master, or if the slave + * configuration pointer is NULL, then the first slave with DC functionality + * will provide the reference clock. + * + * \apiusage{master_idle,blocking} + * + * \return 0 on success, otherwise negative error code. + */ +EC_PUBLIC_API int ecrt_master_select_reference_clock( + ec_master_t *master, /**< EtherCAT master. */ + ec_slave_config_t *sc /**< Slave config of the slave to use as the + * reference slave (or NULL). */ + ); + +/** Obtains master information. + * + * No memory is allocated on the heap in this function. + * + * \apiusage{master_any,rt_safe} + * + * \attention The pointer to this structure must point to a valid variable. + * + * \return 0 in case of success, else < 0 + */ +EC_PUBLIC_API int ecrt_master( + ec_master_t *master, /**< EtherCAT master */ + ec_master_info_t *master_info /**< Structure that will output the + information */ + ); + +/** Obtains network scan progress information. + * + * No memory is allocated on the heap in this function. + * + * \apiusage{master_any,rt_safe} + * + * \attention The pointer to this structure must point to a valid variable. + * + * \return 0 in case of success, else < 0 + */ +EC_PUBLIC_API int ecrt_master_scan_progress( + ec_master_t *master, /**< EtherCAT master */ + ec_master_scan_progress_t *progress /**< Structure that will output + the progress information. */ + ); + +/** Obtains slave information. + * + * Tries to find the slave with the given ring position. The obtained + * information is stored in a structure. No memory is allocated on the heap in + * this function. + * + * \apiusage{master_any,blocking} + * + * \attention The pointer to this structure must point to a valid variable. + * + * \return 0 in case of success, else < 0 + */ +EC_PUBLIC_API int ecrt_master_get_slave( + ec_master_t *master, /**< EtherCAT master */ + uint16_t slave_position, /**< Slave position. */ + ec_slave_info_t *slave_info /**< Structure that will output the + information */ + ); + +#ifndef __KERNEL__ + +/** Returns the proposed configuration of a slave's sync manager. + * + * Fills a given ec_sync_info_t structure with the attributes of a sync + * manager. The \a pdos field of the return value is left empty. Use + * ecrt_master_get_pdo() to get the PDO information. + * + * \apiusage{master_any,blocking} + * + * \return zero on success, else non-zero + */ +EC_PUBLIC_API int ecrt_master_get_sync_manager( + ec_master_t *master, /**< EtherCAT master. */ + uint16_t slave_position, /**< Slave position. */ + uint8_t sync_index, /**< Sync manager index. Must be less + than #EC_MAX_SYNC_MANAGERS. */ + ec_sync_info_t *sync /**< Pointer to output structure. */ + ); + +/** Returns information about a currently assigned PDO. + * + * Fills a given ec_pdo_info_t structure with the attributes of a currently + * assigned PDO of the given sync manager. The \a entries field of the return + * value is left empty. Use ecrt_master_get_pdo_entry() to get the PDO + * entry information. + * + * \apiusage{master_any,blocking} + * + * \retval zero on success, else non-zero + */ +EC_PUBLIC_API int ecrt_master_get_pdo( + ec_master_t *master, /**< EtherCAT master. */ + uint16_t slave_position, /**< Slave position. */ + uint8_t sync_index, /**< Sync manager index. Must be less + than #EC_MAX_SYNC_MANAGERS. */ + uint16_t pos, /**< Zero-based PDO position. */ + ec_pdo_info_t *pdo /**< Pointer to output structure. */ + ); + +/** Returns information about a currently mapped PDO entry. + * + * Fills a given ec_pdo_entry_info_t structure with the attributes of a + * currently mapped PDO entry of the given PDO. + * + * \apiusage{master_any,blocking} + * + * \retval zero on success, else non-zero + */ +EC_PUBLIC_API int ecrt_master_get_pdo_entry( + ec_master_t *master, /**< EtherCAT master. */ + uint16_t slave_position, /**< Slave position. */ + uint8_t sync_index, /**< Sync manager index. Must be less + than #EC_MAX_SYNC_MANAGERS. */ + uint16_t pdo_pos, /**< Zero-based PDO position. */ + uint16_t entry_pos, /**< Zero-based PDO entry position. */ + ec_pdo_entry_info_t *entry /**< Pointer to output structure. */ + ); + +#endif /* #ifndef __KERNEL__ */ + +/** Executes an SDO download request to write data to a slave. + * + * This request is processed by the master state machine. This method blocks, + * until the request has been processed and may not be called in realtime + * context. + * + * \apiusage{master_any,blocking} + * + * \retval 0 Success. + * \retval <0 Error code. + */ +EC_PUBLIC_API int ecrt_master_sdo_download( + ec_master_t *master, /**< EtherCAT master. */ + uint16_t slave_position, /**< Slave position. */ + uint16_t index, /**< Index of the SDO. */ + uint8_t subindex, /**< Subindex of the SDO. */ + const uint8_t *data, /**< Data buffer to download. */ + size_t data_size, /**< Size of the data buffer. */ + uint32_t *abort_code /**< Abort code of the SDO download. */ + ); + +/** Executes an SDO download request to write data to a slave via complete + * access. + * + * This request is processed by the master state machine. This method blocks, + * until the request has been processed and may not be called in realtime + * context. + * + * \apiusage{master_any,blocking} + * + * \retval 0 Success. + * \retval <0 Error code. + */ +EC_PUBLIC_API int ecrt_master_sdo_download_complete( + ec_master_t *master, /**< EtherCAT master. */ + uint16_t slave_position, /**< Slave position. */ + uint16_t index, /**< Index of the SDO. */ + const uint8_t *data, /**< Data buffer to download. */ + size_t data_size, /**< Size of the data buffer. */ + uint32_t *abort_code /**< Abort code of the SDO download. */ + ); + +/** Executes an SDO upload request to read data from a slave. + * + * This request is processed by the master state machine. This method blocks, + * until the request has been processed and may not be called in realtime + * context. + * + * \apiusage{master_any,blocking} + * + * \retval 0 Success. + * \retval <0 Error code. + */ +EC_PUBLIC_API int ecrt_master_sdo_upload( + ec_master_t *master, /**< EtherCAT master. */ + uint16_t slave_position, /**< Slave position. */ + uint16_t index, /**< Index of the SDO. */ + uint8_t subindex, /**< Subindex of the SDO. */ + uint8_t *target, /**< Target buffer for the upload. */ + size_t target_size, /**< Size of the target buffer. */ + size_t *result_size, /**< Uploaded data size. */ + uint32_t *abort_code /**< Abort code of the SDO upload. */ + ); + +/** Executes an SoE write request. + * + * Starts writing an IDN and blocks until the request was processed, or an + * error occurred. + * + * \apiusage{master_any,blocking} + * + * \retval 0 Success. + * \retval <0 Error code. + */ +EC_PUBLIC_API int ecrt_master_write_idn( + ec_master_t *master, /**< EtherCAT master. */ + uint16_t slave_position, /**< Slave position. */ + uint8_t drive_no, /**< Drive number. */ + uint16_t idn, /**< SoE IDN (see ecrt_slave_config_idn()). */ + const uint8_t *data, /**< Pointer to data to write. */ + size_t data_size, /**< Size of data to write. */ + uint16_t *error_code /**< Pointer to variable, where an SoE error code + can be stored. */ + ); + +/** Executes an SoE read request. + * + * Starts reading an IDN and blocks until the request was processed, or an + * error occurred. + * + * \apiusage{master_any,blocking} + * + * \retval 0 Success. + * \retval <0 Error code. + */ +EC_PUBLIC_API int ecrt_master_read_idn( + ec_master_t *master, /**< EtherCAT master. */ + uint16_t slave_position, /**< Slave position. */ + uint8_t drive_no, /**< Drive number. */ + uint16_t idn, /**< SoE IDN (see ecrt_slave_config_idn()). */ + uint8_t *target, /**< Pointer to memory where the read data can be + stored. */ + size_t target_size, /**< Size of the memory \a target points to. */ + size_t *result_size, /**< Actual size of the received data. */ + uint16_t *error_code /**< Pointer to variable, where an SoE error code + can be stored. */ + ); + +/** Finishes the configuration phase and prepares for cyclic operation. + * + * This function tells the master that the configuration phase is finished and + * the realtime operation will begin. The function allocates internal memory + * for the domains and calculates the logical FMMU addresses for domain + * members. It tells the master state machine that the configuration is + * now to be applied to the network. + * + * \apiusage{master_idle,blocking} + * + * \attention After this function has been called, the realtime application is + * in charge of cyclically calling ecrt_master_send() and + * ecrt_master_receive() to ensure network communication. Before calling this + * function, the master thread is responsible for that, so these functions may + * not be called! The method itself allocates memory and should not be called + * in realtime context. + * + * \return 0 in case of success, else < 0 + */ +EC_PUBLIC_API int ecrt_master_activate( + ec_master_t *master /**< EtherCAT master. */ + ); + +/** Deactivates the master. + * + * Removes the master configuration. All objects created by + * ecrt_master_create_domain(), ecrt_master_slave_config(), ecrt_domain_data() + * ecrt_slave_config_create_sdo_request() and + * ecrt_slave_config_create_voe_handler() are freed, so pointers to them + * become invalid. + * + * \apiusage{master_op,blocking} + * + * This method should not be called in realtime context. + * \return 0 on success, otherwise negative error code. + * \retval 0 Success. + * \retval -EINVAL Master has not been activated before. + */ +EC_PUBLIC_API int ecrt_master_deactivate( + ec_master_t *master /**< EtherCAT master. */ + ); + +/** Set interval between calls to ecrt_master_send(). + * + * This information helps the master to decide, how much data can be appended + * to a frame by the master state machine. When the master is configured with + * --enable-hrtimers, this is used to calculate the scheduling of the master + * thread. + * + * \apiusage{master_idle,blocking} + * + * \retval 0 on success. + * \retval <0 Error code. + */ +EC_PUBLIC_API int ecrt_master_set_send_interval( + ec_master_t *master, /**< EtherCAT master. */ + size_t send_interval /**< Send interval in us */ + ); + +/** Sends all datagrams in the queue. + * + * This method takes all datagrams, that have been queued for transmission, + * puts them into frames, and passes them to the Ethernet device for sending. + * + * Has to be called cyclically by the application after ecrt_master_activate() + * has returned. + * + * \apiusage{master_op,rt_safe} + * + * \return Zero on success, otherwise negative error code. + */ +EC_PUBLIC_API int ecrt_master_send( + ec_master_t *master /**< EtherCAT master. */ + ); + +/** Fetches received frames from the hardware and processes the datagrams. + * + * Queries the network device for received frames by calling the interrupt + * service routine. Extracts received datagrams and dispatches the results to + * the datagram objects in the queue. Received datagrams, and the ones that + * timed out, will be marked, and dequeued. + * + * Has to be called cyclically by the realtime application after + * ecrt_master_activate() has returned. + * + * \apiusage{master_op,rt_safe} + * + * \return Zero on success, otherwise negative error code. + */ +EC_PUBLIC_API int ecrt_master_receive( + ec_master_t *master /**< EtherCAT master. */ + ); + +#ifdef __KERNEL__ +/** Sends non-application datagrams. + * + * This method has to be called in the send callback function passed via + * ecrt_master_callbacks() to allow the sending of non-application datagrams. + * + * \apiusage{master_op,rt_safe} + * + * \return Zero on success, otherwise negative error code. + * \retval -EAGAIN Lock could not be acquired, try again later. + */ +int ecrt_master_send_ext( + ec_master_t *master /**< EtherCAT master. */ + ); +#endif + +/** Reads the current master state. + * + * Stores the master state information in the given \a state structure. + * + * This method returns a global state. For the link-specific states in a + * redundant network topology, use the ecrt_master_link_state() method. + * + * \apiusage{master_any,rt_safe} + * + * \return Zero on success, otherwise negative error code. + */ +EC_PUBLIC_API int ecrt_master_state( + const ec_master_t *master, /**< EtherCAT master. */ + ec_master_state_t *state /**< Structure to store the information. */ + ); + +/** Reads the current state of a redundant link. + * + * Stores the link state information in the given \a state structure. + * + * \apiusage{master_any,rt_safe} + * + * \return Zero on success, otherwise negative error code. + */ +EC_PUBLIC_API int ecrt_master_link_state( + const ec_master_t *master, /**< EtherCAT master. */ + unsigned int dev_idx, /**< Index of the device (0 = main device, 1 = + first backup device, ...). */ + ec_master_link_state_t *state /**< Structure to store the information. + */ + ); + +/** Sets the application time. + * + * The master has to know the application's time when operating slaves with + * distributed clocks. The time is not incremented by the master itself, so + * this method has to be called cyclically. + * + * \attention The time passed to this method is used to calculate the phase of + * the slaves' SYNC0/1 interrupts. It should be called constantly at the same + * point of the realtime cycle. So it is recommended to call it at the start + * of the calculations to avoid deviancies due to changing execution times. + * Avoid calling this method before the realtime cycle is established. + * + * The time is used when setting the slaves' System Time Offset and + * Cyclic Operation Start Time registers and when synchronizing the + * DC reference clock to the application time via + * ecrt_master_sync_reference_clock(). + * + * The time is defined as nanoseconds from 2000-01-01 00:00. Converting an + * epoch time can be done with the EC_TIMEVAL2NANO() macro, but is not + * necessary, since the absolute value is not of any interest. + * + * \apiusage{master_op,rt_safe} + * + * \return Zero on success, otherwise negative error code. + */ +EC_PUBLIC_API int ecrt_master_application_time( + ec_master_t *master, /**< EtherCAT master. */ + uint64_t app_time /**< Application time. */ + ); + +/** Queues the DC reference clock drift compensation datagram for sending. + * + * The reference clock will by synchronized to the application time provided + * by the last call off ecrt_master_application_time(). + * + * \apiusage{master_op,rt_safe} + * + * \return Zero on success, otherwise negative error code. + * \retval 0 Success. + * \retval -ENXIO No reference clock found. + */ +EC_PUBLIC_API int ecrt_master_sync_reference_clock( + ec_master_t *master /**< EtherCAT master. */ + ); + +/** Queues the DC reference clock drift compensation datagram for sending. + * + * The reference clock will by synchronized to the time passed in the + * sync_time parameter. + * + * Has to be called by the application after ecrt_master_activate() + * has returned. + * + * \apiusage{master_op,rt_safe} + * + * \return Zero on success, otherwise negative error code. + * \retval 0 Success. + * \retval -ENXIO No reference clock found. + */ +EC_PUBLIC_API int ecrt_master_sync_reference_clock_to( + ec_master_t *master, /**< EtherCAT master. */ + uint64_t sync_time /**< Sync reference clock to this time. */ + ); + +/** Queues the DC clock drift compensation datagram for sending. + * + * All slave clocks synchronized to the reference clock. + * + * Has to be called by the application after ecrt_master_activate() + * has returned. + * + * \apiusage{master_op,rt_safe} + * + * \return 0 on success, otherwise negative error code. + * \retval 0 Success. + * \retval -ENXIO No reference clock found. + */ +EC_PUBLIC_API int ecrt_master_sync_slave_clocks( + ec_master_t *master /**< EtherCAT master. */ + ); + +/** Get the lower 32 bit of the reference clock system time. + * + * This method can be used to synchronize the master to the reference clock. + * + * The reference clock system time is queried via the + * ecrt_master_sync_slave_clocks() method, that reads the system time of the + * reference clock and writes it to the slave clocks (so be sure to call it + * cyclically to get valid data). + * + * \attention The returned time is the system time of the reference clock + * minus the transmission delay of the reference clock. + * + * Calling this method makes only sense in realtime context (after master + * activation), when the ecrt_master_sync_slave_clocks() method is called + * cyclically. + * + * \apiusage{master_op,rt_safe} + * + * \retval 0 success, system time was written into \a time. + * \retval -ENXIO No reference clock found. + * \retval -EIO Slave synchronization datagram was not received. + */ +EC_PUBLIC_API int ecrt_master_reference_clock_time( + const ec_master_t *master, /**< EtherCAT master. */ + uint32_t *time /**< Pointer to store the queried system time. */ + ); + +/** Queues the DC synchrony monitoring datagram for sending. + * + * The datagram broadcast-reads all "System time difference" registers (\a + * 0x092c) to get an upper estimation of the DC synchrony. The result can be + * checked with the ecrt_master_sync_monitor_process() method. + * + * \apiusage{master_op,rt_safe} + * + * \return Zero on success, otherwise a negative error code. + */ +EC_PUBLIC_API int ecrt_master_sync_monitor_queue( + ec_master_t *master /**< EtherCAT master. */ + ); + +/** Processes the DC synchrony monitoring datagram. + * + * If the sync monitoring datagram was sent before with + * ecrt_master_sync_monitor_queue(), the result can be queried with this + * method. + * + * \apiusage{master_op,rt_safe} + * + * \return Upper estimation of the maximum time difference in ns, -1 on error. + * \retval (uint32_t)-1 Error. + */ +EC_PUBLIC_API uint32_t ecrt_master_sync_monitor_process( + const ec_master_t *master /**< EtherCAT master. */ + ); + +/** Retry configuring slaves. + * + * Via this method, the application can tell the master to bring all slaves to + * OP state. In general, this is not necessary, because it is automatically + * done by the master. But with special slaves, that can be reconfigured by + * the vendor during runtime, it can be useful. + * + * Calling this method only makes sense in realtime context (after + * activation), because slaves will not be configured before. + * + * \apiusage{master_op,rt_safe} + * + * \return 0 on success, otherwise negative error code. + */ +EC_PUBLIC_API int ecrt_master_reset( + ec_master_t *master /**< EtherCAT master. */ + ); + +/** Set the SII caching method. + * + * Via this method, the application can tell the master to either which fields + * to use for looking up cached SII content pages or to disable SII caching at + * all. + * + * The default when starting up is defined in the master configuration file. + * The caching method stays valid as long as the master is existing, so it + * could be set by a prior application. + * + * The allowed fields are defined in ec_sii_caching_fields_t. A typical setup + * could be: + * + * \code + * if (ecrt_master_sii_caching(master, + * EC_SII_VENDOR | EC_SII_PRODUCT | EC_SII_REVISION)) { + * fprintf(stderr, "Failed to set up SII caching method.\n"); + * } + * \endcode + * + * A value of zero disables SII caching completely, thus the SII contents are + * completely loaded from every slave during scanning: + * + * \code + * if (ecrt_master_sii_caching(master, EC_SII_DISABLE_CACHING)) { + * fprintf(stderr, "Failed to disable SII caching.\n"); + * } + * \endcode + * + * \apiusage{master_op,rt_safe} + * + * \return 0 on success, otherwise negative error code. + */ +EC_PUBLIC_API int ecrt_master_sii_caching( + ec_master_t *master, /**< EtherCAT master. */ + ec_sii_caching_fields_t fields /** Fields to use for cache lookup. */ + ); + +/***************************************************************************** + * Slave configuration methods + ****************************************************************************/ + +/** Configure a sync manager. + * + * Sets the direction of a sync manager. This overrides the direction bits + * from the default control register from SII. + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * \apiusage{master_idle,blocking} + * + * \return zero on success, else non-zero + */ +EC_PUBLIC_API int ecrt_slave_config_sync_manager( + ec_slave_config_t *sc, /**< Slave configuration. */ + uint8_t sync_index, /**< Sync manager index. Must be less + than #EC_MAX_SYNC_MANAGERS. */ + ec_direction_t direction, /**< Input/Output. */ + ec_watchdog_mode_t watchdog_mode /** Watchdog mode. */ + ); + +/** Configure a slave's watchdog times. + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * \apiusage{master_idle,blocking} + * + * \return 0 on success, otherwise negative error code. + */ +EC_PUBLIC_API int ecrt_slave_config_watchdog( + ec_slave_config_t *sc, /**< Slave configuration. */ + uint16_t watchdog_divider, /**< Number of 40 ns intervals (register + 0x0400). Used as a base unit for all + slave watchdogs^. If set to zero, the + value is not written, so the default is + used. */ + uint16_t watchdog_intervals /**< Number of base intervals for sync + manager watchdog (register 0x0420). If + set to zero, the value is not written, + so the default is used. */ + ); + +/** Add a PDO to a sync manager's PDO assignment. + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * \apiusage{master_idle,blocking} + * + * \see ecrt_slave_config_pdos() + * \return zero on success, else non-zero + */ +EC_PUBLIC_API int ecrt_slave_config_pdo_assign_add( + ec_slave_config_t *sc, /**< Slave configuration. */ + uint8_t sync_index, /**< Sync manager index. Must be less + than #EC_MAX_SYNC_MANAGERS. */ + uint16_t index /**< Index of the PDO to assign. */ + ); + +/** Clear a sync manager's PDO assignment. + * + * This can be called before assigning PDOs via + * ecrt_slave_config_pdo_assign_add(), to clear the default assignment of a + * sync manager. + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * \apiusage{master_idle,blocking} + * + * \see ecrt_slave_config_pdos() + * \return 0 on success, otherwise negative error code. + */ +EC_PUBLIC_API int ecrt_slave_config_pdo_assign_clear( + ec_slave_config_t *sc, /**< Slave configuration. */ + uint8_t sync_index /**< Sync manager index. Must be less + than #EC_MAX_SYNC_MANAGERS. */ + ); + +/** Add a PDO entry to the given PDO's mapping. + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * \apiusage{master_idle,blocking} + * + * \see ecrt_slave_config_pdos() + * \return zero on success, else non-zero + */ +EC_PUBLIC_API int ecrt_slave_config_pdo_mapping_add( + ec_slave_config_t *sc, /**< Slave configuration. */ + uint16_t pdo_index, /**< Index of the PDO. */ + uint16_t entry_index, /**< Index of the PDO entry to add to the PDO's + mapping. */ + uint8_t entry_subindex, /**< Subindex of the PDO entry to add to the + PDO's mapping. */ + uint8_t entry_bit_length /**< Size of the PDO entry in bit. */ + ); + +/** Clear the mapping of a given PDO. + * + * This can be called before mapping PDO entries via + * ecrt_slave_config_pdo_mapping_add(), to clear the default mapping. + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * \apiusage{master_idle,blocking} + * + * \see ecrt_slave_config_pdos() + * \return 0 on success, otherwise negative error code. + */ +EC_PUBLIC_API int ecrt_slave_config_pdo_mapping_clear( + ec_slave_config_t *sc, /**< Slave configuration. */ + uint16_t pdo_index /**< Index of the PDO. */ + ); + +/** Specify a complete PDO configuration. + * + * This function is a convenience wrapper for the functions + * ecrt_slave_config_sync_manager(), ecrt_slave_config_pdo_assign_clear(), + * ecrt_slave_config_pdo_assign_add(), ecrt_slave_config_pdo_mapping_clear() + * and ecrt_slave_config_pdo_mapping_add(), that are better suitable for + * automatic code generation. + * + * The following example shows, how to specify a complete configuration, + * including the PDO mappings. With this information, the master is able to + * reserve the complete process data, even if the slave is not present at + * configuration time: + * + * \code + * ec_pdo_entry_info_t el3162_channel1[] = { + * {0x3101, 1, 8}, // status + * {0x3101, 2, 16} // value + * }; + * + * ec_pdo_entry_info_t el3162_channel2[] = { + * {0x3102, 1, 8}, // status + * {0x3102, 2, 16} // value + * }; + * + * ec_pdo_info_t el3162_pdos[] = { + * {0x1A00, 2, el3162_channel1}, + * {0x1A01, 2, el3162_channel2} + * }; + * + * ec_sync_info_t el3162_syncs[] = { + * {2, EC_DIR_OUTPUT}, + * {3, EC_DIR_INPUT, 2, el3162_pdos}, + * {0xff} + * }; + * + * if (ecrt_slave_config_pdos(sc_ana_in, EC_END, el3162_syncs)) { + * // handle error + * } + * \endcode + * + * The next example shows, how to configure the PDO assignment only. The + * entries for each assigned PDO are taken from the PDO's default mapping. + * Please note, that PDO entry registration will fail, if the PDO + * configuration is left empty and the slave is offline. + * + * \code + * ec_pdo_info_t pdos[] = { + * {0x1600}, // Channel 1 + * {0x1601} // Channel 2 + * }; + * + * ec_sync_info_t syncs[] = { + * {3, EC_DIR_INPUT, 2, pdos}, + * }; + * + * if (ecrt_slave_config_pdos(slave_config_ana_in, 1, syncs)) { + * // handle error + * } + * \endcode + * + * Processing of \a syncs will stop, if + * - the number of processed items reaches \a n_syncs, or + * - the \a index member of an ec_sync_info_t item is 0xff. In this case, + * \a n_syncs should set to a number greater than the number of list items; + * using EC_END is recommended. + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * \apiusage{master_idle,blocking} + * + * \return zero on success, else non-zero + */ +EC_PUBLIC_API int ecrt_slave_config_pdos( + ec_slave_config_t *sc, /**< Slave configuration. */ + unsigned int n_syncs, /**< Number of sync manager configurations in + \a syncs. */ + const ec_sync_info_t syncs[] /**< Array of sync manager + configurations. */ + ); + +/** Registers a PDO entry for process data exchange in a domain. + * + * Searches the assigned PDOs for the given PDO entry. An error is raised, if + * the given entry is not mapped. Otherwise, the corresponding sync manager + * and FMMU configurations are provided for slave configuration and the + * respective sync manager's assigned PDOs are appended to the given domain, + * if not already done. The offset of the requested PDO entry's data inside + * the domain's process data is returned. Optionally, the PDO entry bit + * position (0-7) can be retrieved via the \a bit_position output parameter. + * This pointer may be \a NULL, in this case an error is raised if the PDO + * entry does not byte-align. + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * \apiusage{master_idle,blocking} + * + * \retval >=0 Success: Offset of the PDO entry's process data. + * \retval <0 Error code. + */ +EC_PUBLIC_API int ecrt_slave_config_reg_pdo_entry( + ec_slave_config_t *sc, /**< Slave configuration. */ + uint16_t entry_index, /**< Index of the PDO entry to register. */ + uint8_t entry_subindex, /**< Subindex of the PDO entry to register. */ + ec_domain_t *domain, /**< Domain. */ + unsigned int *bit_position /**< Optional address if bit addressing + is desired */ + ); + +/** Registers a PDO entry using its position. + * + * Similar to ecrt_slave_config_reg_pdo_entry(), but not using PDO indices but + * offsets in the PDO mapping, because PDO entry indices may not be unique + * inside a slave's PDO mapping. An error is raised, if + * one of the given positions is out of range. + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * \apiusage{master_idle,blocking} + * + * \retval >=0 Success: Offset of the PDO entry's process data. + * \retval <0 Error code. + */ +EC_PUBLIC_API int ecrt_slave_config_reg_pdo_entry_pos( + ec_slave_config_t *sc, /**< Slave configuration. */ + uint8_t sync_index, /**< Sync manager index. */ + unsigned int pdo_pos, /**< Position of the PDO inside the SM. */ + unsigned int entry_pos, /**< Position of the entry inside the PDO. */ + ec_domain_t *domain, /**< Domain. */ + unsigned int *bit_position /**< Optional address if bit addressing + is desired */ + ); + +/** Configure distributed clocks. + * + * Sets the AssignActivate word and the cycle and shift times for the sync + * signals. + * + * The AssignActivate word is vendor-specific and can be taken from the XML + * device description file (Device -> Dc -> AssignActivate). Set this to zero, + * if the slave shall be operated without distributed clocks (default). + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * \apiusage{master_idle,blocking} + * + * \attention The \a sync1_shift time is ignored. + * \return 0 on success, otherwise negative error code. + */ +EC_PUBLIC_API int ecrt_slave_config_dc( + ec_slave_config_t *sc, /**< Slave configuration. */ + uint16_t assign_activate, /**< AssignActivate word. */ + uint32_t sync0_cycle, /**< SYNC0 cycle time [ns]. */ + int32_t sync0_shift, /**< SYNC0 shift time [ns]. */ + uint32_t sync1_cycle, /**< SYNC1 cycle time [ns]. */ + int32_t sync1_shift /**< SYNC1 shift time [ns]. */ + ); + +/** Add an SDO configuration. + * + * An SDO configuration is stored in the slave configuration object and is + * downloaded to the slave whenever the slave is being configured by the + * master. This usually happens once on master activation, but can be repeated + * subsequently, for example after the slave's power supply failed. + * + * \attention The SDOs for PDO assignment (\p 0x1C10 - \p 0x1C2F) and PDO + * mapping (\p 0x1600 - \p 0x17FF and \p 0x1A00 - \p 0x1BFF) should not be + * configured with this function, because they are part of the slave + * configuration done by the master. Please use ecrt_slave_config_pdos() and + * friends instead. + * + * This is the generic function for adding an SDO configuration. Please note + * that the this function does not do any endianness correction. If + * datatype-specific functions are needed (that automatically correct the + * endianness), have a look at ecrt_slave_config_sdo8(), + * ecrt_slave_config_sdo16() and ecrt_slave_config_sdo32(). + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * \apiusage{master_idle,blocking} + * + * \retval 0 Success. + * \retval <0 Error code. + */ +EC_PUBLIC_API int ecrt_slave_config_sdo( + ec_slave_config_t *sc, /**< Slave configuration. */ + uint16_t index, /**< Index of the SDO to configure. */ + uint8_t subindex, /**< Subindex of the SDO to configure. */ + const uint8_t *data, /**< Pointer to the data. */ + size_t size /**< Size of the \a data. */ + ); + +/** Add a configuration value for an 8-bit SDO. + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * \see ecrt_slave_config_sdo(). + * + * \apiusage{master_idle,blocking} + * + * \retval 0 Success. + * \retval <0 Error code. + */ +EC_PUBLIC_API int ecrt_slave_config_sdo8( + ec_slave_config_t *sc, /**< Slave configuration */ + uint16_t sdo_index, /**< Index of the SDO to configure. */ + uint8_t sdo_subindex, /**< Subindex of the SDO to configure. */ + uint8_t value /**< Value to set. */ + ); + +/** Add a configuration value for a 16-bit SDO. + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * \see ecrt_slave_config_sdo(). + * + * \apiusage{master_idle,blocking} + * + * \retval 0 Success. + * \retval <0 Error code. + */ +EC_PUBLIC_API int ecrt_slave_config_sdo16( + ec_slave_config_t *sc, /**< Slave configuration */ + uint16_t sdo_index, /**< Index of the SDO to configure. */ + uint8_t sdo_subindex, /**< Subindex of the SDO to configure. */ + uint16_t value /**< Value to set. */ + ); + +/** Add a configuration value for a 32-bit SDO. + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * \see ecrt_slave_config_sdo(). + * + * \apiusage{master_idle,blocking} + * + * \retval 0 Success. + * \retval <0 Error code. + */ +EC_PUBLIC_API int ecrt_slave_config_sdo32( + ec_slave_config_t *sc, /**< Slave configuration */ + uint16_t sdo_index, /**< Index of the SDO to configure. */ + uint8_t sdo_subindex, /**< Subindex of the SDO to configure. */ + uint32_t value /**< Value to set. */ + ); + +/** Add configuration data for a complete SDO. + * + * The SDO data are transferred via CompleteAccess. Data for the first + * subindex (0) have to be included. + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * \see ecrt_slave_config_sdo(). + * + * \apiusage{master_idle,blocking} + * + * \retval 0 Success. + * \retval <0 Error code. + */ +EC_PUBLIC_API int ecrt_slave_config_complete_sdo( + ec_slave_config_t *sc, /**< Slave configuration. */ + uint16_t index, /**< Index of the SDO to configure. */ + const uint8_t *data, /**< Pointer to the data. */ + size_t size /**< Size of the \a data. */ + ); + +/** Set the size of the CoE emergency ring buffer. + * + * The initial size is zero, so all messages will be dropped. This method can + * be called even after master activation, but it will clear the ring buffer! + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * \apiusage{master_idle,blocking} + * + * \return 0 on success, or negative error code. + */ +EC_PUBLIC_API int ecrt_slave_config_emerg_size( + ec_slave_config_t *sc, /**< Slave configuration. */ + size_t elements /**< Number of records of the CoE emergency ring. */ + ); + +/** Read and remove one record from the CoE emergency ring buffer. + * + * A record consists of 8 bytes: + * + * Byte 0-1: Error code (little endian) + * Byte 2: Error register + * Byte 3-7: Data + * + * Calling this method makes only sense in realtime context (after master + * activation). + * + * \return 0 on success (record popped), or negative error code (i. e. + * -ENOENT, if ring is empty). + * + * \apiusage{master_op,any_context} + */ +EC_PUBLIC_API int ecrt_slave_config_emerg_pop( + ec_slave_config_t *sc, /**< Slave configuration. */ + uint8_t *target /**< Pointer to target memory (at least + EC_COE_EMERGENCY_MSG_SIZE bytes). */ + ); + +/** Clears CoE emergency ring buffer and the overrun counter. + * + * Calling this method makes only sense in realtime context (after master + * activation). + * + * \apiusage{master_op,any_context} + * + * \return 0 on success, or negative error code. + * + */ +EC_PUBLIC_API int ecrt_slave_config_emerg_clear( + ec_slave_config_t *sc /**< Slave configuration. */ + ); + +/** Read the number of CoE emergency overruns. + * + * The overrun counter will be incremented when a CoE emergency message could + * not be stored in the ring buffer and had to be dropped. Call + * ecrt_slave_config_emerg_clear() to reset the counter. + * + * Calling this method makes only sense in realtime context (after master + * activation). + * + * \apiusage{master_op,any_context} + * + * \return Number of overruns since last clear, or negative error code. + * + */ +EC_PUBLIC_API int ecrt_slave_config_emerg_overruns( + const ec_slave_config_t *sc /**< Slave configuration. */ + ); + +/** Create an SDO request to exchange SDOs during realtime operation. + * + * The created SDO request object is freed automatically when the master is + * released. + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * \apiusage{master_idle,blocking} + * + * \return New SDO request, or NULL on error. + */ +EC_PUBLIC_API ec_sdo_request_t *ecrt_slave_config_create_sdo_request( + ec_slave_config_t *sc, /**< Slave configuration. */ + uint16_t index, /**< SDO index. */ + uint8_t subindex, /**< SDO subindex. */ + size_t size /**< Data size to reserve. */ + ); + +/** Create an SoE request to exchange SoE IDNs during realtime operation. + * + * The created SoE request object is freed automatically when the master is + * released. + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * \apiusage{master_idle,blocking} + * + * \return New SoE request, or NULL on error. + */ +EC_PUBLIC_API ec_soe_request_t *ecrt_slave_config_create_soe_request( + ec_slave_config_t *sc, /**< Slave configuration. */ + uint8_t drive_no, /**< Drive number. */ + uint16_t idn, /**< Sercos ID-Number. */ + size_t size /**< Data size to reserve. */ + ); + +/** Create an VoE handler to exchange vendor-specific data during realtime + * operation. + * + * The number of VoE handlers per slave configuration is not limited, but + * usually it is enough to create one for sending and one for receiving, if + * both can be done simultaneously. + * + * The created VoE handler object is freed automatically when the master is + * released. + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * \apiusage{master_idle,blocking} + * + * \return New VoE handler, or NULL on error. + */ +EC_PUBLIC_API ec_voe_handler_t *ecrt_slave_config_create_voe_handler( + ec_slave_config_t *sc, /**< Slave configuration. */ + size_t size /**< Data size to reserve. */ + ); + +/** Create a register request to exchange EtherCAT register contents during + * realtime operation. + * + * This interface should not be used to take over master functionality, + * instead it is intended for debugging and monitoring reasons. + * + * The created register request object is freed automatically when the master + * is released. + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * \apiusage{master_idle,blocking} + * + * \return New register request, or NULL on error. + */ +EC_PUBLIC_API ec_reg_request_t *ecrt_slave_config_create_reg_request( + ec_slave_config_t *sc, /**< Slave configuration. */ + size_t size /**< Data size to reserve. */ + ); + +/** Outputs the state of the slave configuration. + * + * Stores the state information in the given \a state structure. The state + * information is updated by the master state machine, so it may take a few + * cycles, until it changes. + * + * \attention If the state of process data exchange shall be monitored in + * realtime, ecrt_domain_state() should be used. + * + * \apiusage{master_op,rt_safe} + * + * This method is meant to be called in realtime context (after master + * activation). + * + * \retval 0 Success. + * \retval <0 Error code. + */ +EC_PUBLIC_API int ecrt_slave_config_state( + const ec_slave_config_t *sc, /**< Slave configuration */ + ec_slave_config_state_t *state /**< State object to write to. */ + ); + +/** Add an SoE IDN configuration. + * + * A configuration for a Sercos-over-EtherCAT IDN is stored in the slave + * configuration object and is written to the slave whenever the slave is + * being configured by the master. This usually happens once on master + * activation, but can be repeated subsequently, for example after the slave's + * power supply failed. + * + * The \a idn parameter can be separated into several sections: + * - Bit 15: Standard data (0) or Product data (1) + * - Bit 14 - 12: Parameter set (0 - 7) + * - Bit 11 - 0: Data block number (0 - 4095) + * + * Please note that the this function does not do any endianness correction. + * Multi-byte data have to be passed in EtherCAT endianness (little-endian). + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * \apiusage{master_idle,blocking} + * + * \retval 0 Success. + * \retval <0 Error code. + */ +EC_PUBLIC_API int ecrt_slave_config_idn( + ec_slave_config_t *sc, /**< Slave configuration. */ + uint8_t drive_no, /**< Drive number. */ + uint16_t idn, /**< SoE IDN. */ + ec_al_state_t state, /**< AL state in which to write the IDN (PREOP or + SAFEOP). */ + const uint8_t *data, /**< Pointer to the data. */ + size_t size /**< Size of the \a data. */ + ); + +/** Adds a feature flag to a slave configuration. + * + * Feature flags are a generic way to configure slave-specific behavior. + * + * Multiple calls with the same slave configuration and key will overwrite the + * configuration. + * + * The following flags may be available: + * - AssignToPdi: Zero (default) keeps the slave information interface (SII) + * assigned to EtherCAT (except during transition to PREOP). Non-zero + * assigns the SII to the slave controller side before going to PREOP and + * leaves it there until a write command happens. + * - WaitBeforeSAFEOPms: Number of milliseconds to wait before commanding the + * transition from PREOP to SAFEOP. This can be used as a workaround for + * slaves that need a little time to initialize. + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * \apiusage{master_idle,blocking} + * + * \retval 0 Success. + * \retval <0 Error code. + */ +EC_PUBLIC_API int ecrt_slave_config_flag( + ec_slave_config_t *sc, /**< Slave configuration. */ + const char *key, /**< Key as null-terminated ASCII string. */ + int32_t value /**< Value to store. */ + ); + +/** Sets the link/MAC address for Ethernet-over-EtherCAT (EoE) operation. + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * The MAC address is stored in the slave configuration object and will be + * written to the slave during the configuration process. + * + * \apiusage{master_idle,blocking} + * + * \retval 0 Success. + * \retval <0 Error code. + */ +EC_PUBLIC_API int ecrt_slave_config_eoe_mac_address( + ec_slave_config_t *sc, /**< Slave configuration. */ + const unsigned char *mac_address /**< MAC address. */ + ); + +/** Sets the IP address for Ethernet-over-EtherCAT (EoE) operation. + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * The IP address is stored in the slave configuration object and will be + * written to the slave during the configuration process. + * + * The IP address is passed by-value as a `struct in_addr`. This structure + * contains the 32-bit IPv4 address in network byte order (big endian). + * + * A string-represented IPv4 address can be converted to a `struct in_addr` + * for example via the POSIX function `inet_pton()` (see man 3 inet_pton): + * + * \code{.c} + * #include + * struct in_addr addr; + * if (inet_aton("192.168.0.1", &addr) == 0) { + * fprintf(stderr, "Failed to convert IP address.\n"); + * return -1; + * } + * if (ecrt_slave_config_eoe_ip_address(sc, addr)) { + * fprintf(stderr, "Failed to set IP address.\n"); + * return -1; + * } + * \endcode + * + * + * \apiusage{master_idle,blocking} + * + * \retval 0 Success. + * \retval <0 Error code. + */ +EC_PUBLIC_API int ecrt_slave_config_eoe_ip_address( + ec_slave_config_t *sc, /**< Slave configuration. */ + struct in_addr ip_address /**< IPv4 address. */ + ); + +/** Sets the subnet mask for Ethernet-over-EtherCAT (EoE) operation. + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * The subnet mask is stored in the slave configuration object and will be + * written to the slave during the configuration process. + * + * The subnet mask is passed by-value as a `struct in_addr`. This structure + * contains the 32-bit mask in network byte order (big endian). + * + * See ecrt_slave_config_eoe_ip_address() on how to convert string-coded masks + * to `struct in_addr`. + * + * \apiusage{master_idle,blocking} + * + * \retval 0 Success. + * \retval <0 Error code. + */ +EC_PUBLIC_API int ecrt_slave_config_eoe_subnet_mask( + ec_slave_config_t *sc, /**< Slave configuration. */ + struct in_addr subnet_mask /**< IPv4 subnet mask. */ + ); + +/** Sets the gateway address for Ethernet-over-EtherCAT (EoE) operation. + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * The gateway address is stored in the slave configuration object and will be + * written to the slave during the configuration process. + * + * The address is passed by-value as a `struct in_addr`. This structure + * contains the 32-bit IPv4 address in network byte order (big endian). + * + * See ecrt_slave_config_eoe_ip_address() on how to convert string-coded IPv4 + * addresses to `struct in_addr`. + * + * \apiusage{master_idle,blocking} + * + * \retval 0 Success. + * \retval <0 Error code. + */ +EC_PUBLIC_API int ecrt_slave_config_eoe_default_gateway( + ec_slave_config_t *sc, /**< Slave configuration. */ + struct in_addr gateway_address /**< Gateway's IPv4 address. */ + ); + +/** Sets the IPv4 address of the DNS server for Ethernet-over-EtherCAT (EoE) + * operation. + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * The DNS server address is stored in the slave configuration object and will + * be written to the slave during the configuration process. + * + * The address is passed by-value as a `struct in_addr`. This structure + * contains the 32-bit IPv4 address in network byte order (big endian). + * + * See ecrt_slave_config_eoe_ip_address() on how to convert string-coded IPv4 + * addresses to `struct in_addr`. + * + * \apiusage{master_idle,blocking} + * + * \retval 0 Success. + * \retval <0 Error code. + */ +EC_PUBLIC_API int ecrt_slave_config_eoe_dns_address( + ec_slave_config_t *sc, /**< Slave configuration. */ + struct in_addr dns_address /**< IPv4 address of the DNS server. */ + ); + +/** Sets the host name for Ethernet-over-EtherCAT (EoE) operation. + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * The host name is stored in the slave configuration object and will + * be written to the slave during the configuration process. + * + * The maximum size of the host name is 32 bytes (including the zero + * terminator). + * + * \apiusage{master_idle,blocking} + * + * \retval 0 Success. + * \retval <0 Error code. + */ +EC_PUBLIC_API int ecrt_slave_config_eoe_hostname( + ec_slave_config_t *sc, /**< Slave configuration. */ + const char *name /**< Zero-terminated host name. */ + ); + +/** Sets the application-layer state transition timeout in ms. + * + * Change the maximum allowed time for a slave to make an application-layer + * state transition for the given state transition (for example from PREOP to + * SAFEOP). The default values are defined in ETG.2000. + * + * A timeout value of zero ms will restore the default value. + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * \apiusage{master_idle,blocking} + * + * \retval 0 Success. + * \retval <0 Error code. + */ +EC_PUBLIC_API int ecrt_slave_config_state_timeout( + ec_slave_config_t *sc, /**< Slave configuration. */ + ec_al_state_t from_state, /**< Initial state. */ + ec_al_state_t to_state, /**< Target state. */ + unsigned int timeout_ms /**< Timeout in [ms]. */ + ); + +/***************************************************************************** + * Domain methods + ****************************************************************************/ + +/** Registers a bunch of PDO entries for a domain. + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * \see ecrt_slave_config_reg_pdo_entry() + * + * \attention The registration array has to be terminated with an empty + * structure, or one with the \a index field set to zero! + * + * \apiusage{master_idle,blocking} + * + * \return 0 on success, else non-zero. + */ +EC_PUBLIC_API int ecrt_domain_reg_pdo_entry_list( + ec_domain_t *domain, /**< Domain. */ + const ec_pdo_entry_reg_t *pdo_entry_regs /**< Array of PDO + registrations. */ + ); + +/** Returns the current size of the domain's process data. + * + * The domain size is calculated after master activation. + * + * \apiusage{master_op,rt_safe} + * + * \return Size of the process data image, or a negative error code. + */ +EC_PUBLIC_API size_t ecrt_domain_size( + const ec_domain_t *domain /**< Domain. */ + ); + +#ifdef __KERNEL__ + +/** Provide external memory to store the domain's process data. + * + * Call this after all PDO entries have been registered and before activating + * the master. + * + * The size of the allocated memory must be at least ecrt_domain_size(), after + * all PDO entries have been registered. + * + * This method has to be called in non-realtime context before + * ecrt_master_activate(). + * + * \apiusage{master_idle,blocking} + */ +void ecrt_domain_external_memory( + ec_domain_t *domain, /**< Domain. */ + uint8_t *memory /**< Address of the memory to store the process + data in. */ + ); + +#endif /* __KERNEL__ */ + +/** Returns the domain's process data. + * + * - In kernel context: If external memory was provided with + * ecrt_domain_external_memory(), the returned pointer will contain the + * address of that memory. Otherwise it will point to the internally allocated + * memory. In the latter case, this method may not be called before + * ecrt_master_activate(). + * + * - In userspace context: This method has to be called after + * ecrt_master_activate() to get the mapped domain process data memory. + * + * \apiusage{master_op,rt_safe} + * + * \return Pointer to the process data memory. + */ +EC_PUBLIC_API uint8_t *ecrt_domain_data( + const ec_domain_t *domain /**< Domain. */ + ); + +/** Determines the states of the domain's datagrams. + * + * Evaluates the working counters of the received datagrams and outputs + * statistics, if necessary. This must be called after ecrt_master_receive() + * is expected to receive the domain datagrams in order to make + * ecrt_domain_state() return the result of the last process data exchange. + * + * \apiusage{master_op,rt_safe} + * + * \return 0 on success, otherwise negative error code. + */ +EC_PUBLIC_API int ecrt_domain_process( + ec_domain_t *domain /**< Domain. */ + ); + +/** (Re-)queues all domain datagrams in the master's datagram queue. + * + * Call this function to mark the domain's datagrams for exchanging at the + * next call of ecrt_master_send(). + * + * \apiusage{master_op,rt_safe} + * + * \return 0 on success, otherwise negative error code. + */ +EC_PUBLIC_API int ecrt_domain_queue( + ec_domain_t *domain /**< Domain. */ + ); + +/** Reads the state of a domain. + * + * Stores the domain state in the given \a state structure. + * + * Using this method, the process data exchange can be monitored in realtime. + * + * \apiusage{master_op,rt_safe} + * + * \return 0 on success, otherwise negative error code. + */ +EC_PUBLIC_API int ecrt_domain_state( + const ec_domain_t *domain, /**< Domain. */ + ec_domain_state_t *state /**< Pointer to a state object to store the + information. */ + ); + +/***************************************************************************** + * SDO request methods. + ****************************************************************************/ + +/** Set the SDO index and subindex. + * + * \attention If the SDO index and/or subindex is changed while + * ecrt_sdo_request_state() returns EC_REQUEST_BUSY, this may lead to + * unexpected results. + * + * This method is meant to be called in realtime context (after master + * activation). To initialize the SDO request, the index and subindex can be + * set via ecrt_slave_config_create_sdo_request(). + * + * \apiusage{master_op,rt_safe} + * + * \return 0 on success, otherwise negative error code. + */ +EC_PUBLIC_API int ecrt_sdo_request_index( + ec_sdo_request_t *req, /**< SDO request. */ + uint16_t index, /**< SDO index. */ + uint8_t subindex /**< SDO subindex. */ + ); + +/** Set the timeout for an SDO request. + * + * If the request cannot be processed in the specified time, if will be marked + * as failed. + * + * The timeout is permanently stored in the request object and is valid until + * the next call of this method. + * + * The timeout should be defined in non-realtime context, but can also be + * changed afterwards. + * + * \apiusage{master_any,rt_safe} + * + * \return 0 on success, otherwise negative error code. + */ +EC_PUBLIC_API int ecrt_sdo_request_timeout( + ec_sdo_request_t *req, /**< SDO request. */ + uint32_t timeout /**< Timeout in milliseconds. Zero means no + timeout. */ + ); + +/** Access to the SDO request's data. + * + * This function returns a pointer to the request's internal SDO data memory. + * + * - After a read operation was successful, integer data can be evaluated + * using the EC_READ_*() macros as usual. Example: + * \code + * uint16_t value = EC_READ_U16(ecrt_sdo_request_data(sdo))); + * \endcode + * - If a write operation shall be triggered, the data have to be written to + * the internal memory. Use the EC_WRITE_*() macros, if you are writing + * integer data. Be sure, that the data fit into the memory. The memory size + * is a parameter of ecrt_slave_config_create_sdo_request(). + * \code + * EC_WRITE_U16(ecrt_sdo_request_data(sdo), 0xFFFF); + * \endcode + * + * \attention The return value can be invalid during a read operation, because + * the internal SDO data memory could be re-allocated if the read SDO data do + * not fit inside. + * + * This method is meant to be called in realtime context (after master + * activation), but can also be used to initialize data before. + * + * \apiusage{master_any,rt_safe} + * + * \return Pointer to the internal SDO data memory. + * + */ +EC_PUBLIC_API uint8_t *ecrt_sdo_request_data( + const ec_sdo_request_t *req /**< SDO request. */ + ); + +/** Returns the current SDO data size. + * + * When the SDO request is created, the data size is set to the size of the + * reserved memory. After a read operation the size is set to the size of the + * read data. The size is not modified in any other situation. + * + * This method is meant to be called in realtime context (after master + * activation). + * + * \apiusage{master_any,rt_safe} + * + * \return SDO data size in bytes. + * + */ +EC_PUBLIC_API size_t ecrt_sdo_request_data_size( + const ec_sdo_request_t *req /**< SDO request. */ + ); + +/** Get the current state of the SDO request. + * + * The user-space implementation fetches incoming data and stores the received + * data size in the request object, so the request is not const. + * + * This method is meant to be called in realtime context (after master + * activation). + * + * \apiusage{master_op,rt_safe} + * + * \return Request state. + * + */ +EC_PUBLIC_API ec_request_state_t ecrt_sdo_request_state( +#ifdef __KERNEL__ + const +#endif + ec_sdo_request_t *req /**< SDO request. */ + ); + +/** Schedule an SDO write operation. + * + * \attention This method may not be called while ecrt_sdo_request_state() + * returns EC_REQUEST_BUSY. + * + * This method is meant to be called in realtime context (after master + * activation). + * + * \apiusage{master_op,rt_safe} + * + * \return 0 on success, otherwise negative error code. + * \retval -EINVAL Invalid input data, e.g. data size == 0. + * \retval -ENOBUFS Reserved memory in ecrt_slave_config_create_sdo_request() + * too small. + */ +EC_PUBLIC_API int ecrt_sdo_request_write( + ec_sdo_request_t *req /**< SDO request. */ + ); + +/** Schedule an SDO read operation. + * + * \attention This method may not be called while ecrt_sdo_request_state() + * returns EC_REQUEST_BUSY. + * + * \attention After calling this function, the return value of + * ecrt_sdo_request_data() must be considered as invalid while + * ecrt_sdo_request_state() returns EC_REQUEST_BUSY. + * + * This method is meant to be called in realtime context (after master + * activation). + * + * \apiusage{master_op,rt_safe} + * + * \return 0 on success, otherwise negative error code. + */ +EC_PUBLIC_API int ecrt_sdo_request_read( + ec_sdo_request_t *req /**< SDO request. */ + ); + +/***************************************************************************** + * SoE request methods. + ****************************************************************************/ + +/** Set the request's drive and Sercos ID numbers. + * + * \attention If the drive number and/or IDN is changed while + * ecrt_soe_request_state() returns EC_REQUEST_BUSY, this may lead to + * unexpected results. + * + * This method is meant to be called in realtime context (after master + * activation). To initialize the SoE request, the drive_no and IDN can be + * set via ecrt_slave_config_create_soe_request(). + * + * \apiusage{master_op,rt_safe} + * + * \return 0 on success, otherwise negative error code. + */ +EC_PUBLIC_API int ecrt_soe_request_idn( + ec_soe_request_t *req, /**< IDN request. */ + uint8_t drive_no, /**< SDO index. */ + uint16_t idn /**< SoE IDN. */ + ); + +/** Set the timeout for an SoE request. + * + * If the request cannot be processed in the specified time, if will be marked + * as failed. + * + * The timeout is permanently stored in the request object and is valid until + * the next call of this method. + * + * The timeout should be defined in non-realtime context, but can also be + * changed afterwards. + * + * \apiusage{master_any,rt_safe} + * + * \return 0 on success, otherwise negative error code. + */ +EC_PUBLIC_API int ecrt_soe_request_timeout( + ec_soe_request_t *req, /**< SoE request. */ + uint32_t timeout /**< Timeout in milliseconds. Zero means no + timeout. */ + ); + +/** Access to the SoE request's data. + * + * This function returns a pointer to the request's internal IDN data memory. + * + * - After a read operation was successful, integer data can be evaluated + * using the EC_READ_*() macros as usual. Example: + * \code + * uint16_t value = EC_READ_U16(ecrt_soe_request_data(idn_req))); + * \endcode + * - If a write operation shall be triggered, the data have to be written to + * the internal memory. Use the EC_WRITE_*() macros, if you are writing + * integer data. Be sure, that the data fit into the memory. The memory size + * is a parameter of ecrt_slave_config_create_soe_request(). + * \code + * EC_WRITE_U16(ecrt_soe_request_data(idn_req), 0xFFFF); + * \endcode + * + * \attention The return value can be invalidated during a read operation, + * because the internal IDN data memory could be re-allocated if the read IDN + * data do not fit inside. + * + * This method is meant to be called in realtime context (after master + * activation), but can also be used to initialize data before. + * + * \apiusage{master_any,rt_safe} + * + * \return Pointer to the internal IDN data memory. + * + */ +EC_PUBLIC_API uint8_t *ecrt_soe_request_data( + const ec_soe_request_t *req /**< SoE request. */ + ); + +/** Returns the current IDN data size. + * + * When the SoE request is created, the data size is set to the size of the + * reserved memory. After a read operation the size is set to the size of the + * read data. The size is not modified in any other situation. + * + * \apiusage{master_any,rt_safe} + * + * \return IDN data size in bytes. + */ +EC_PUBLIC_API size_t ecrt_soe_request_data_size( + const ec_soe_request_t *req /**< SoE request. */ + ); + +/** Get the current state of the SoE request. + * + * \return Request state. + * + * This method is meant to be called in realtime context (after master + * activation). + * + * In the user-space implementation, the method fetches the size of the + * incoming data, so the request object is not const. + * + * \apiusage{master_op,rt_safe} + */ +EC_PUBLIC_API ec_request_state_t ecrt_soe_request_state( +#ifdef __KERNEL__ + const +#endif + ec_soe_request_t *req /**< SoE request. */ + ); + +/** Schedule an SoE IDN write operation. + * + * \attention This method may not be called while ecrt_soe_request_state() + * returns EC_REQUEST_BUSY. + * + * This method is meant to be called in realtime context (after master + * activation). + * + * \apiusage{master_op,rt_safe} + * + * \return 0 on success, otherwise negative error code. + * \retval -EINVAL Invalid input data, e.g. data size == 0. + * \retval -ENOBUFS Reserved memory in ecrt_slave_config_create_soe_request() + * too small. + */ +EC_PUBLIC_API int ecrt_soe_request_write( + ec_soe_request_t *req /**< SoE request. */ + ); + +/** Schedule an SoE IDN read operation. + * + * \attention This method may not be called while ecrt_soe_request_state() + * returns EC_REQUEST_BUSY. + * + * \attention After calling this function, the return value of + * ecrt_soe_request_data() must be considered as invalid while + * ecrt_soe_request_state() returns EC_REQUEST_BUSY. + * + * This method is meant to be called in realtime context (after master + * activation). + * + * \apiusage{master_op,rt_safe} + * + * \return 0 on success, otherwise negative error code. + */ +EC_PUBLIC_API int ecrt_soe_request_read( + ec_soe_request_t *req /**< SoE request. */ + ); + +/***************************************************************************** + * VoE handler methods. + ****************************************************************************/ + +/** Sets the VoE header for future send operations. + * + * A VoE message shall contain a 4-byte vendor ID, followed by a 2-byte vendor + * type at as header. These numbers can be set with this function. The values + * are valid and will be used for future send operations until the next call + * of this method. + * + * This method is meant to be called in non-realtime context (before master + * activation) to initialize the header data, but it is also safe to + * change the header later on in realtime context. + * + * \apiusage{master_any,rt_safe} + * + * \return 0 on success, otherwise negative error code. + */ +EC_PUBLIC_API int ecrt_voe_handler_send_header( + ec_voe_handler_t *voe, /**< VoE handler. */ + uint32_t vendor_id, /**< Vendor ID. */ + uint16_t vendor_type /**< Vendor-specific type. */ + ); + +/** Reads the header data of a received VoE message. + * + * This method can be used to get the received VoE header information after a + * read operation has succeeded. + * + * The header information is stored at the memory given by the pointer + * parameters. + * + * This method is meant to be called in realtime context (after master + * activation). + * + * \apiusage{master_op,rt_safe} + * + * \return 0 on success, otherwise negative error code. + */ +EC_PUBLIC_API int ecrt_voe_handler_received_header( + const ec_voe_handler_t *voe, /**< VoE handler. */ + uint32_t *vendor_id, /**< Vendor ID. */ + uint16_t *vendor_type /**< Vendor-specific type. */ + ); + +/** Access to the VoE handler's data. + * + * This function returns a pointer to the VoE handler's internal memory, that + * points to the actual VoE data right after the VoE header (see + * ecrt_voe_handler_send_header()). + * + * - After a read operation was successful, the memory contains the received + * data. The size of the received data can be determined via + * ecrt_voe_handler_data_size(). + * - Before a write operation is triggered, the data have to be written to the + * internal memory. Be sure, that the data fit into the memory. The reserved + * memory size is a parameter of ecrt_slave_config_create_voe_handler(). + * + * \attention The returned pointer is not necessarily persistent: After a read + * operation, the internal memory may have been reallocated. This can be + * avoided by reserving enough memory via the \a size parameter of + * ecrt_slave_config_create_voe_handler(). + * + * \apiusage{master_any,rt_safe} + * + * \return Pointer to the internal memory. + */ +EC_PUBLIC_API uint8_t *ecrt_voe_handler_data( + const ec_voe_handler_t *voe /**< VoE handler. */ + ); + +/** Returns the current data size. + * + * The data size is the size of the VoE data without the header (see + * ecrt_voe_handler_send_header()). + * + * When the VoE handler is created, the data size is set to the size of the + * reserved memory. At a write operation, the data size is set to the number + * of bytes to write. After a read operation the size is set to the size of + * the read data. The size is not modified in any other situation. + * + * \apiusage{master_any,rt_safe} + * + * \return Data size in bytes. + */ +EC_PUBLIC_API size_t ecrt_voe_handler_data_size( + const ec_voe_handler_t *voe /**< VoE handler. */ + ); + +/** Start a VoE write operation. + * + * After this function has been called, the ecrt_voe_handler_execute() method + * must be called in every realtime cycle as long as it returns + * EC_REQUEST_BUSY. No other operation may be started while the handler is + * busy. + * + * This method is meant to be called in realtime context (after master + * activation). + * + * \apiusage{master_op,rt_safe} + * + * \return 0 on success, otherwise negative error code. + * \retval -ENOBUFS Reserved memory in ecrt_slave_config_create_voe_handler + * too small. + */ +EC_PUBLIC_API int ecrt_voe_handler_write( + ec_voe_handler_t *voe, /**< VoE handler. */ + size_t size /**< Number of bytes to write (without the VoE header). */ + ); + +/** Start a VoE read operation. + * + * After this function has been called, the ecrt_voe_handler_execute() method + * must be called in every realtime cycle as long as it returns + * EC_REQUEST_BUSY. No other operation may be started while the handler is + * busy. + * + * The state machine queries the slave's send mailbox for new data to be send + * to the master. If no data appear within the EC_VOE_RESPONSE_TIMEOUT + * (defined in master/voe_handler.c), the operation fails. + * + * On success, the size of the read data can be determined via + * ecrt_voe_handler_data_size(), while the VoE header of the received data + * can be retrieved with ecrt_voe_handler_received_header(). + * + * This method is meant to be called in realtime context (after master + * activation). + * + * \apiusage{master_op,rt_safe} + * + * \return 0 on success, otherwise negative error code. + */ +EC_PUBLIC_API int ecrt_voe_handler_read( + ec_voe_handler_t *voe /**< VoE handler. */ + ); + +/** Start a VoE read operation without querying the sync manager status. + * + * After this function has been called, the ecrt_voe_handler_execute() method + * must be called in every realtime cycle as long as it returns + * EC_REQUEST_BUSY. No other operation may be started while the handler is + * busy. + * + * The state machine queries the slave by sending an empty mailbox. The slave + * fills its data to the master in this mailbox. If no data appear within the + * EC_VOE_RESPONSE_TIMEOUT (defined in master/voe_handler.c), the operation + * fails. + * + * On success, the size of the read data can be determined via + * ecrt_voe_handler_data_size(), while the VoE header of the received data + * can be retrieved with ecrt_voe_handler_received_header(). + * + * This method is meant to be called in realtime context (after master + * activation). + * + * \apiusage{master_op,rt_safe} + * + * \return 0 on success, otherwise negative error code. + */ +EC_PUBLIC_API int ecrt_voe_handler_read_nosync( + ec_voe_handler_t *voe /**< VoE handler. */ + ); + +/** Execute the handler. + * + * This method executes the VoE handler. It has to be called in every realtime + * cycle as long as it returns EC_REQUEST_BUSY. + * + * \return Handler state. + * + * This method is meant to be called in realtime context (after master + * activation). + * + * \apiusage{master_op,rt_safe} + * + */ +EC_PUBLIC_API ec_request_state_t ecrt_voe_handler_execute( + ec_voe_handler_t *voe /**< VoE handler. */ + ); + +/***************************************************************************** + * Register request methods. + ****************************************************************************/ + +/** Access to the register request's data. + * + * This function returns a pointer to the request's internal memory. + * + * - After a read operation was successful, integer data can be evaluated + * using the EC_READ_*() macros as usual. Example: + * \code + * uint16_t value = EC_READ_U16(ecrt_reg_request_data(reg_request))); + * \endcode + * - If a write operation shall be triggered, the data have to be written to + * the internal memory. Use the EC_WRITE_*() macros, if you are writing + * integer data. Be sure, that the data fit into the memory. The memory size + * is a parameter of ecrt_slave_config_create_reg_request(). + * \code + * EC_WRITE_U16(ecrt_reg_request_data(reg_request), 0xFFFF); + * \endcode + * + * This method is meant to be called in realtime context (after master + * activation), but can also be used to initialize data before. + * + * \apiusage{master_any,rt_safe} + * + * \return Pointer to the internal memory. + * + */ +EC_PUBLIC_API uint8_t *ecrt_reg_request_data( + const ec_reg_request_t *req /**< Register request. */ + ); + +/** Get the current state of the register request. + * + * This method is meant to be called in realtime context (after master + * activation). + * + * \apiusage{master_op,rt_safe} + * + * \return Request state. + * + */ +EC_PUBLIC_API ec_request_state_t ecrt_reg_request_state( + const ec_reg_request_t *req /**< Register request. */ + ); + +/** Schedule an register write operation. + * + * \attention This method may not be called while ecrt_reg_request_state() + * returns EC_REQUEST_BUSY. + * + * \attention The \a size parameter is truncated to the size given at request + * creation. + * + * This method is meant to be called in realtime context (after master + * activation). + * + * \apiusage{master_op,rt_safe} + * + * \return 0 on success, otherwise negative error code. + * \retval -ENOBUFS Reserved memory in ecrt_slave_config_create_reg_request + * too small. + */ +EC_PUBLIC_API int ecrt_reg_request_write( + ec_reg_request_t *req, /**< Register request. */ + uint16_t address, /**< Register address. */ + size_t size /**< Size to write. */ + ); + +/** Schedule a register read operation. + * + * \attention This method may not be called while ecrt_reg_request_state() + * returns EC_REQUEST_BUSY. + * + * \attention The \a size parameter is truncated to the size given at request + * creation. + * + * This method is meant to be called in realtime context (after master + * activation). + * + * \apiusage{master_op,rt_safe} + * + * \return 0 on success, otherwise negative error code. + * \retval -ENOBUFS Reserved memory in ecrt_slave_config_create_reg_request + * too small. + */ +EC_PUBLIC_API int ecrt_reg_request_read( + ec_reg_request_t *req, /**< Register request. */ + uint16_t address, /**< Register address. */ + size_t size /**< Size to write. */ + ); + +/***************************************************************************** + * Bitwise read/write macros + ****************************************************************************/ + +/** Read a certain bit of an EtherCAT data byte. + * + * \param DATA EtherCAT data pointer + * \param POS bit position + */ +#define EC_READ_BIT(DATA, POS) ((*((uint8_t *) (DATA)) >> (POS)) & 0x01) + +/** Write a certain bit of an EtherCAT data byte. + * + * \param DATA EtherCAT data pointer + * \param POS bit position + * \param VAL new bit value + */ +#define EC_WRITE_BIT(DATA, POS, VAL) \ + do { \ + if (VAL) *((uint8_t *) (DATA)) |= (1 << (POS)); \ + else *((uint8_t *) (DATA)) &= ~(1 << (POS)); \ + } while (0) + +/***************************************************************************** + * Byte-swapping functions for user space + ****************************************************************************/ + +#ifndef __KERNEL__ + +#if __BYTE_ORDER == __LITTLE_ENDIAN + +#define le16_to_cpu(x) x +#define le32_to_cpu(x) x +#define le64_to_cpu(x) x + +#define cpu_to_le16(x) x +#define cpu_to_le32(x) x +#define cpu_to_le64(x) x + +#elif __BYTE_ORDER == __BIG_ENDIAN + +#define swap16(x) \ + ((uint16_t)( \ + (((uint16_t)(x) & 0x00ffU) << 8) | \ + (((uint16_t)(x) & 0xff00U) >> 8) )) +#define swap32(x) \ + ((uint32_t)( \ + (((uint32_t)(x) & 0x000000ffUL) << 24) | \ + (((uint32_t)(x) & 0x0000ff00UL) << 8) | \ + (((uint32_t)(x) & 0x00ff0000UL) >> 8) | \ + (((uint32_t)(x) & 0xff000000UL) >> 24) )) +#define swap64(x) \ + ((uint64_t)( \ + (((uint64_t)(x) & 0x00000000000000ffULL) << 56) | \ + (((uint64_t)(x) & 0x000000000000ff00ULL) << 40) | \ + (((uint64_t)(x) & 0x0000000000ff0000ULL) << 24) | \ + (((uint64_t)(x) & 0x00000000ff000000ULL) << 8) | \ + (((uint64_t)(x) & 0x000000ff00000000ULL) >> 8) | \ + (((uint64_t)(x) & 0x0000ff0000000000ULL) >> 24) | \ + (((uint64_t)(x) & 0x00ff000000000000ULL) >> 40) | \ + (((uint64_t)(x) & 0xff00000000000000ULL) >> 56) )) + +#define le16_to_cpu(x) swap16(x) +#define le32_to_cpu(x) swap32(x) +#define le64_to_cpu(x) swap64(x) + +#define cpu_to_le16(x) swap16(x) +#define cpu_to_le32(x) swap32(x) +#define cpu_to_le64(x) swap64(x) + +#endif + +#define le16_to_cpup(x) le16_to_cpu(*((uint16_t *)(x))) +#define le32_to_cpup(x) le32_to_cpu(*((uint32_t *)(x))) +#define le64_to_cpup(x) le64_to_cpu(*((uint64_t *)(x))) + +#endif /* ifndef __KERNEL__ */ + +/***************************************************************************** + * Read macros + ****************************************************************************/ + +/** Read an 8-bit unsigned value from EtherCAT data. + * + * \return EtherCAT data value + */ +#define EC_READ_U8(DATA) \ + ((uint8_t) *((uint8_t *) (DATA))) + +/** Read an 8-bit signed value from EtherCAT data. + * + * \param DATA EtherCAT data pointer + * \return EtherCAT data value + */ +#define EC_READ_S8(DATA) \ + ((int8_t) *((uint8_t *) (DATA))) + +/** Read a 16-bit unsigned value from EtherCAT data. + * + * \param DATA EtherCAT data pointer + * \return EtherCAT data value + */ +#define EC_READ_U16(DATA) \ + ((uint16_t) le16_to_cpup((void *) (DATA))) + +/** Read a 16-bit signed value from EtherCAT data. + * + * \param DATA EtherCAT data pointer + * \return EtherCAT data value + */ +#define EC_READ_S16(DATA) \ + ((int16_t) le16_to_cpup((void *) (DATA))) + +/** Read a 32-bit unsigned value from EtherCAT data. + * + * \param DATA EtherCAT data pointer + * \return EtherCAT data value + */ +#define EC_READ_U32(DATA) \ + ((uint32_t) le32_to_cpup((void *) (DATA))) + +/** Read a 32-bit signed value from EtherCAT data. + * + * \param DATA EtherCAT data pointer + * \return EtherCAT data value + */ +#define EC_READ_S32(DATA) \ + ((int32_t) le32_to_cpup((void *) (DATA))) + +/** Read a 64-bit unsigned value from EtherCAT data. + * + * \param DATA EtherCAT data pointer + * \return EtherCAT data value + */ +#define EC_READ_U64(DATA) \ + ((uint64_t) le64_to_cpup((void *) (DATA))) + +/** Read a 64-bit signed value from EtherCAT data. + * + * \param DATA EtherCAT data pointer + * \return EtherCAT data value + */ +#define EC_READ_S64(DATA) \ + ((int64_t) le64_to_cpup((void *) (DATA))) + +/***************************************************************************** + * Floating-point read functions and macros (userspace only) + ****************************************************************************/ + +#ifndef __KERNEL__ + +/** Read a 32-bit floating-point value from EtherCAT data. + * + * \apiusage{master_any,rt_safe} + * + * \param data EtherCAT data pointer + * \return EtherCAT data value + */ +EC_PUBLIC_API float ecrt_read_real(const void *data); + +/** Read a 32-bit floating-point value from EtherCAT data. + * + * \param DATA EtherCAT data pointer + * \return EtherCAT data value + */ +#define EC_READ_REAL(DATA) ecrt_read_real(DATA) + +/** Read a 64-bit floating-point value from EtherCAT data. + * + * \apiusage{master_any,rt_safe} + * + * \param data EtherCAT data pointer + * \return EtherCAT data value + */ +EC_PUBLIC_API double ecrt_read_lreal(const void *data); + +/** Read a 64-bit floating-point value from EtherCAT data. + * + * \param DATA EtherCAT data pointer + * \return EtherCAT data value + */ +#define EC_READ_LREAL(DATA) ecrt_read_lreal(DATA) + +#endif // ifndef __KERNEL__ + +/***************************************************************************** + * Write macros + ****************************************************************************/ + +/** Write an 8-bit unsigned value to EtherCAT data. + * + * \param DATA EtherCAT data pointer + * \param VAL new value + */ +#define EC_WRITE_U8(DATA, VAL) \ + do { \ + *((uint8_t *)(DATA)) = ((uint8_t) (VAL)); \ + } while (0) + +/** Write an 8-bit signed value to EtherCAT data. + * + * \param DATA EtherCAT data pointer + * \param VAL new value + */ +#define EC_WRITE_S8(DATA, VAL) EC_WRITE_U8(DATA, VAL) + +/** Write a 16-bit unsigned value to EtherCAT data. + * + * \param DATA EtherCAT data pointer + * \param VAL new value + */ +#define EC_WRITE_U16(DATA, VAL) \ + do { \ + *((uint16_t *) (DATA)) = cpu_to_le16((uint16_t) (VAL)); \ + } while (0) + +/** Write a 16-bit signed value to EtherCAT data. + * + * \param DATA EtherCAT data pointer + * \param VAL new value + */ +#define EC_WRITE_S16(DATA, VAL) EC_WRITE_U16(DATA, VAL) + +/** Write a 32-bit unsigned value to EtherCAT data. + * + * \param DATA EtherCAT data pointer + * \param VAL new value + */ +#define EC_WRITE_U32(DATA, VAL) \ + do { \ + *((uint32_t *) (DATA)) = cpu_to_le32((uint32_t) (VAL)); \ + } while (0) + +/** Write a 32-bit signed value to EtherCAT data. + * + * \param DATA EtherCAT data pointer + * \param VAL new value + */ +#define EC_WRITE_S32(DATA, VAL) EC_WRITE_U32(DATA, VAL) + +/** Write a 64-bit unsigned value to EtherCAT data. + * + * \param DATA EtherCAT data pointer + * \param VAL new value + */ +#define EC_WRITE_U64(DATA, VAL) \ + do { \ + *((uint64_t *) (DATA)) = cpu_to_le64((uint64_t) (VAL)); \ + } while (0) + +/** Write a 64-bit signed value to EtherCAT data. + * + * \param DATA EtherCAT data pointer + * \param VAL new value + */ +#define EC_WRITE_S64(DATA, VAL) EC_WRITE_U64(DATA, VAL) + +/***************************************************************************** + * Floating-point write functions and macros (userspace only) + ****************************************************************************/ + +#ifndef __KERNEL__ + +/** Write a 32-bit floating-point value to EtherCAT data. + * + * \apiusage{master_any,rt_safe} + * + * \param data EtherCAT data pointer + * \param value new value + */ +EC_PUBLIC_API void ecrt_write_real(void *data, float value); + +/** Write a 32-bit floating-point value to EtherCAT data. + * + * \param DATA EtherCAT data pointer + * \param VAL new value + */ +#define EC_WRITE_REAL(DATA, VAL) ecrt_write_real(DATA, VAL) + +/** Write a 64-bit floating-point value to EtherCAT data. + * + * \apiusage{master_any,rt_safe} + * + * \param data EtherCAT data pointer + * \param value new value + */ +EC_PUBLIC_API void ecrt_write_lreal(void *data, double value); + +/** Write a 64-bit floating-point value to EtherCAT data. + * + * \param DATA EtherCAT data pointer + * \param VAL new value + */ +#define EC_WRITE_LREAL(DATA, VAL) ecrt_write_lreal(DATA, VAL) + +#endif // ifndef __KERNEL__ + +/****************************************************************************/ + +#ifdef __cplusplus +} +#endif + +/****************************************************************************/ + +/** @} */ + +#endif diff --git a/dependency/x86/third_party/ethercat/v1.7.0/include/ectty.h b/dependency/x86/third_party/ethercat/v1.7.0/include/ectty.h new file mode 100644 index 00000000..3178d3ce --- /dev/null +++ b/dependency/x86/third_party/ethercat/v1.7.0/include/ectty.h @@ -0,0 +1,106 @@ +/***************************************************************************** + * + * Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH + * + * This file is part of the IgH EtherCAT master userspace library. + * + * The IgH EtherCAT master userspace library is free software; you can + * redistribute it and/or modify it under the terms of the GNU Lesser General + * Public License as published by the Free Software Foundation; version 2.1 + * of the License. + * + * The IgH EtherCAT master userspace library is distributed in the hope that + * it will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the IgH EtherCAT master userspace library. If not, see + * . + * + ****************************************************************************/ + +/** \file + * + * EtherCAT virtual TTY interface. + * + * \defgroup TTYInterface EtherCAT Virtual TTY Interface + * + * @{ + */ + +/****************************************************************************/ + +#ifndef __ECTTY_H__ +#define __ECTTY_H__ + +#include + +/***************************************************************************** + * Data types + ****************************************************************************/ + +struct ec_tty; +typedef struct ec_tty ec_tty_t; /**< \see ec_tty */ + +/** Operations on the virtual TTY interface. + */ +typedef struct { + int (*cflag_changed)(void *, tcflag_t); /**< Called when the serial + * settings shall be changed. The + * \a cflag argument contains the + * new settings. */ +} ec_tty_operations_t; + +/***************************************************************************** + * Global functions + ****************************************************************************/ + +/** Create a virtual TTY interface. + * + * \param ops Set of callbacks. + * \param cb_data Arbitrary data, that is passed to any callback. + * + * \return Pointer to the interface object, otherwise an ERR_PTR value. + */ +ec_tty_t *ectty_create( + const ec_tty_operations_t *ops, + void *cb_data + ); + +/***************************************************************************** + * TTY interface methods + ****************************************************************************/ + +/** Releases a virtual TTY interface. + */ +void ectty_free( + ec_tty_t *tty /**< TTY interface. */ + ); + +/** Reads data to send from the TTY interface. + * + * If there are data to send, they are copied into the \a buffer. At maximum, + * \a size bytes are copied. The actual number of bytes copied is returned. + * + * \return Number of bytes copied. + */ +unsigned int ectty_tx_data( + ec_tty_t *tty, /**< TTY interface. */ + uint8_t *buffer, /**< Buffer for data to transmit. */ + size_t size /**< Available space in \a buffer. */ + ); + +/** Pushes received data to the TTY interface. + */ +void ectty_rx_data( + ec_tty_t *tty, /**< TTY interface. */ + const uint8_t *buffer, /**< Buffer with received data. */ + size_t size /**< Number of bytes in \a buffer. */ + ); + +/****************************************************************************/ + +/** @} */ + +#endif diff --git a/dependency/x86/third_party/ethercat/v1.7.0/lib/cmake/ethercat/ethercat-config.cmake b/dependency/x86/third_party/ethercat/v1.7.0/lib/cmake/ethercat/ethercat-config.cmake new file mode 100644 index 00000000..49f8e81c --- /dev/null +++ b/dependency/x86/third_party/ethercat/v1.7.0/lib/cmake/ethercat/ethercat-config.cmake @@ -0,0 +1,43 @@ +#---------------------------------------------------------------------------- +# +# Copyright (C) 2021 Bjarne von Horn, Ingenieurgemeinschaft IgH +# +# This file is part of the IgH EtherCAT Master. +# +# The IgH EtherCAT Master is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 2, as +# published by the Free Software Foundation. +# +# The IgH EtherCAT Master is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with the IgH EtherCAT Master; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# +# vim: tw=78 +# +#---------------------------------------------------------------------------- + + +find_library(EtherCAT_LIBRARY + NAMES ethercat + PATHS /home/lgv/cmvr/0-workspace/cmvr-es/dependency/x86/third_party/ethercat/v1.7.0/lib +) + +find_path(EtherCAT_INCLUDE_DIR + NAMES ecrt.h + PATHS /home/lgv/cmvr/0-workspace/cmvr-es/dependency/x86/third_party/ethercat/v1.7.0/include +) + +mark_as_advanced(EtherCAT_LIBRARY EtherCAT_INCLUDE_DIR) + +if(NOT TARGET EtherLab::ethercat) + add_library(EtherLab::ethercat SHARED IMPORTED) + set_target_properties(EtherLab::ethercat PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${EtherCAT_INCLUDE_DIR}" + IMPORTED_LOCATION "${EtherCAT_LIBRARY}" + ) +endif() diff --git a/dependency/x86/third_party/ethercat/v1.7.0/lib/libethercat.a b/dependency/x86/third_party/ethercat/v1.7.0/lib/libethercat.a new file mode 100644 index 00000000..5a47c1d7 Binary files /dev/null and b/dependency/x86/third_party/ethercat/v1.7.0/lib/libethercat.a differ diff --git a/dependency/x86/third_party/ethercat/v1.7.0/lib/libethercat.la b/dependency/x86/third_party/ethercat/v1.7.0/lib/libethercat.la new file mode 100755 index 00000000..056b3b88 --- /dev/null +++ b/dependency/x86/third_party/ethercat/v1.7.0/lib/libethercat.la @@ -0,0 +1,41 @@ +# libethercat.la - a libtool library file +# Generated by libtool (GNU libtool) 2.4.6 Debian-2.4.6-15build2 +# +# Please DO NOT delete this file! +# It is necessary for linking the library. + +# The name that we can dlopen(3). +dlname='libethercat.so.1' + +# Names of this library. +library_names='libethercat.so.1.2.0 libethercat.so.1 libethercat.so' + +# The name of the static archive. +old_library='libethercat.a' + +# Linker flags that cannot go in dependency_libs. +inherited_linker_flags='' + +# Libraries that this one depends upon. +dependency_libs='' + +# Names of additional weak libraries provided by this library +weak_library_names='' + +# Version information for libethercat. +current=3 +age=2 +revision=0 + +# Is this an already installed library? +installed=yes + +# Should we warn about portability when linking against -modules? +shouldnotlink=no + +# Files to dlopen/dlpreopen +dlopen='' +dlpreopen='' + +# Directory that this library needs to be installed in: +libdir='/home/lgv/cmvr/0-workspace/cmvr-es/dependency/x86/third_party/ethercat/v1.7.0/lib' diff --git a/dependency/x86/third_party/ethercat/v1.7.0/lib/libethercat.so b/dependency/x86/third_party/ethercat/v1.7.0/lib/libethercat.so new file mode 120000 index 00000000..d85df380 --- /dev/null +++ b/dependency/x86/third_party/ethercat/v1.7.0/lib/libethercat.so @@ -0,0 +1 @@ +libethercat.so.1.2.0 \ No newline at end of file diff --git a/dependency/x86/third_party/ethercat/v1.7.0/lib/libethercat.so.1 b/dependency/x86/third_party/ethercat/v1.7.0/lib/libethercat.so.1 new file mode 120000 index 00000000..d85df380 --- /dev/null +++ b/dependency/x86/third_party/ethercat/v1.7.0/lib/libethercat.so.1 @@ -0,0 +1 @@ +libethercat.so.1.2.0 \ No newline at end of file diff --git a/dependency/x86/third_party/ethercat/v1.7.0/lib/libethercat.so.1.2.0 b/dependency/x86/third_party/ethercat/v1.7.0/lib/libethercat.so.1.2.0 new file mode 100755 index 00000000..5cdc92f2 Binary files /dev/null and b/dependency/x86/third_party/ethercat/v1.7.0/lib/libethercat.so.1.2.0 differ diff --git a/dependency/x86/third_party/ethercat/v1.7.0/lib/modules/6.8.0-124-generic/ethercat/devices/ec_generic.ko b/dependency/x86/third_party/ethercat/v1.7.0/lib/modules/6.8.0-124-generic/ethercat/devices/ec_generic.ko new file mode 100644 index 00000000..8af80416 Binary files /dev/null and b/dependency/x86/third_party/ethercat/v1.7.0/lib/modules/6.8.0-124-generic/ethercat/devices/ec_generic.ko differ diff --git a/dependency/x86/third_party/ethercat/v1.7.0/lib/modules/6.8.0-124-generic/ethercat/examples/mini/ec_mini.ko b/dependency/x86/third_party/ethercat/v1.7.0/lib/modules/6.8.0-124-generic/ethercat/examples/mini/ec_mini.ko new file mode 100644 index 00000000..0c4e304e Binary files /dev/null and b/dependency/x86/third_party/ethercat/v1.7.0/lib/modules/6.8.0-124-generic/ethercat/examples/mini/ec_mini.ko differ diff --git a/dependency/x86/third_party/ethercat/v1.7.0/lib/modules/6.8.0-124-generic/ethercat/master/ec_master.ko b/dependency/x86/third_party/ethercat/v1.7.0/lib/modules/6.8.0-124-generic/ethercat/master/ec_master.ko new file mode 100644 index 00000000..a5bc2c41 Binary files /dev/null and b/dependency/x86/third_party/ethercat/v1.7.0/lib/modules/6.8.0-124-generic/ethercat/master/ec_master.ko differ diff --git a/dependency/x86/third_party/ethercat/v1.7.0/lib/pkgconfig/libethercat.pc b/dependency/x86/third_party/ethercat/v1.7.0/lib/pkgconfig/libethercat.pc new file mode 100644 index 00000000..de20cf9f --- /dev/null +++ b/dependency/x86/third_party/ethercat/v1.7.0/lib/pkgconfig/libethercat.pc @@ -0,0 +1,34 @@ +# +# pkgconfig file for ethercat library +# +# Copyright 2021 Bjarne von Horn (vh at igh dot de) +# +# This file is part of the ethercat library. +# +# The ethercat library is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# The ethercat library is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +# License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with the ethercat library. If not, see . +# +# vim: tw=78 noexpandtab +# + +prefix=/home/lgv/cmvr/0-workspace/cmvr-es/dependency/x86/third_party/ethercat/v1.7.0 +exec_prefix=${prefix} +libdir=/home/lgv/cmvr/0-workspace/cmvr-es/dependency/x86/third_party/ethercat/v1.7.0/lib +includedir=/home/lgv/cmvr/0-workspace/cmvr-es/dependency/x86/third_party/ethercat/v1.7.0/include + +Name: libethercat +Description: Client support library for the EtherCAT Master +URL: http://www.etherlab.org +Version: 1.7.0 +Libs: -L${libdir} -lethercat +Cflags: -I${includedir} diff --git a/dependency/x86/third_party/ethercat/v1.7.0/lib/systemd/system/ethercat.service b/dependency/x86/third_party/ethercat/v1.7.0/lib/systemd/system/ethercat.service new file mode 100644 index 00000000..116b36cf --- /dev/null +++ b/dependency/x86/third_party/ethercat/v1.7.0/lib/systemd/system/ethercat.service @@ -0,0 +1,38 @@ +# +# EtherCAT master kernel modules +# + +[Unit] +Description=EtherCAT Master Kernel Modules + +# Fine tuning of the startup dependencies below are recommended +# to provide a reliable startup routine. +# The dependencies below can be either uncommented after copying +# this file to /etc/systemd/system or by creating overrides: +# Copy the needed dependencies into +# /etc/systemd/system/ethercat.service.d/50-dependencies.conf +# in a [Unit] section. + +# +# Uncomment this, if the generic Ethernet driver is used. It assures, that the +# network interfaces are configured, before the master starts. +# +#Requires=network.target # Stop master, if network is stopped +#After=network.target # Start master, after network is ready + +# +# Uncomment this, if a native Ethernet driver is used. It assures, that the +# network interfaces are configured, after the Ethernet drivers have been +# replaced. Otherwise, the networking configuration tools could be confused. +# +#Before=network-pre.target +#Wants=network-pre.target + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/home/lgv/cmvr/0-workspace/cmvr-es/dependency/x86/third_party/ethercat/v1.7.0/sbin/ethercatctl start +ExecStop=/home/lgv/cmvr/0-workspace/cmvr-es/dependency/x86/third_party/ethercat/v1.7.0/sbin/ethercatctl stop + +[Install] +WantedBy=multi-user.target diff --git a/dependency/x86/third_party/ethercat/v1.7.0/sbin/ethercatctl b/dependency/x86/third_party/ethercat/v1.7.0/sbin/ethercatctl new file mode 100755 index 00000000..b455d52b --- /dev/null +++ b/dependency/x86/third_party/ethercat/v1.7.0/sbin/ethercatctl @@ -0,0 +1,253 @@ +#!/bin/bash + +#------------------------------------------------------------------------------ +# +# Start script for EtherCAT to use with systemd +# +# Copyright (C) 2006-2021 Florian Pose, Ingenieurgemeinschaft IgH +# +# This file is part of the IgH EtherCAT Master. +# +# The IgH EtherCAT Master is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 2, as +# published by the Free Software Foundation. +# +# The IgH EtherCAT Master is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with the IgH EtherCAT Master; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# +# vim: expandtab sw=4 tw=78 +# +#------------------------------------------------------------------------------ + +LSMOD="/sbin/lsmod" +MODPROBE="/sbin/modprobe" +RMMOD="/sbin/rmmod" +MODINFO="/sbin/modinfo" +IP="/sbin/ip" + +ETHERCAT="/home/lgv/cmvr/0-workspace/cmvr-es/dependency/x86/third_party/ethercat/v1.7.0/bin/ethercat" + +#------------------------------------------------------------------------------ + +if [ "$1" = "-c" ]; then + ETHERCAT_CONFIG="$2" + COMMAND="$3" +else + ETHERCAT_CONFIG="/home/lgv/cmvr/0-workspace/cmvr-es/dependency/x86/third_party/ethercat/v1.7.0/etc/ethercat.conf" + COMMAND="$1" +fi + +#------------------------------------------------------------------------------ + +if [ ! -r ${ETHERCAT_CONFIG} ]; then + echo ${ETHERCAT_CONFIG} not existing; + exit 6 +fi + +# shellcheck source=/etc/ethercat.conf +. ${ETHERCAT_CONFIG} + +#------------------------------------------------------------------------------ + +is_mac_address() { + local x='[0-9a-fA-F]' + echo "$1" | grep -qE "^($x$x:){5}$x$x\$" - +} + +#------------------------------------------------------------------------------ + +parse_mac_address() { + local DEVICENAMETOMAC + if [ -z "${1}" ] || is_mac_address "${1}"; then + MAC="${1}" + else + DEVICENAMETOMAC=$("${IP}" address show dev "${1}" | + awk '/link\/ether/ { print $2; }') + if is_mac_address "${DEVICENAMETOMAC}"; then + MAC="${DEVICENAMETOMAC}" + else + echo Invalid MAC address or interface name \""${1}"\" \ + in ${ETHERCAT_CONFIG} + exit 1 + fi + fi +} + +#------------------------------------------------------------------------------ + +case "$COMMAND" in + +start) + # bring up all updown interfaces before anything else + for interface in $UPDOWN_INTERFACES; do + $IP link set dev $interface up + done + + # construct DEVICES and BACKUPS from configuration variables + DEVICES="" + BACKUPS="" + MASTER_INDEX=0 + + while true; do + DEVICE=$(eval echo "\${MASTER${MASTER_INDEX}_DEVICE}") + BACKUP=$(eval echo "\${MASTER${MASTER_INDEX}_BACKUP}") + if [ -z "${DEVICE}" ]; then break; fi + + if [ ${MASTER_INDEX} -gt 0 ]; then + DEVICES=${DEVICES}, + BACKUPS=${BACKUPS}, + fi + + parse_mac_address "${DEVICE}" + DEVICES=${DEVICES}${MAC} + + parse_mac_address "${BACKUP}" + BACKUPS=${BACKUPS}${MAC} + + MASTER_INDEX=$((${MASTER_INDEX} + 1)) + done + + if [ -z "${DEVICES}" ]; then + echo "ERROR: No network cards for EtherCAT specified." + echo -n "Please edit ${ETHERCAT_CONFIG} with root permissions" + echo -n " and set MASTER0_DEVICE variable to either a " + echo "network interface name (like eth0) or to a MAC address." + exit 1 + fi + + MODULE_PARAMS=( + main_devices="${DEVICES}" + backup_devices="${BACKUPS}" + ) + + if [ -n "$SII_CACHING" ]; then + MODULE_PARAMS+=(sii_caching="$SII_CACHING") + fi + + # load master module + if ! ${MODPROBE} ${MODPROBE_FLAGS} ec_master "${MODULE_PARAMS[@]}"; then + exit 1 + fi + + LOADED_MODULES=ec_master + + # check for modules to replace + for MODULE in ${DEVICE_MODULES}; do + ECMODULE=ec_${MODULE} + if ! ${MODINFO} "${ECMODULE}" > /dev/null; then + continue # ec_* module not found + fi + + if [ "${MODULE}" != "generic" ] && [ "${MODULE}" != "ccat" ]; then + # unload standard module and check if unloading was successful + ${RMMOD} "${MODULE}" 2> /dev/null || true + if ${LSMOD} | grep "^${MODULE//-/_} " > /dev/null; then + # could not unload module + ${RMMOD} ${LOADED_MODULES} + exit 1 + fi + fi + + if ! ${MODPROBE} ${MODPROBE_FLAGS} "${ECMODULE}"; then + if [ "${MODULE}" != "generic" ] && [ "${MODULE}" != "ccat" ]; then + ${MODPROBE} ${MODPROBE_FLAGS} "${MODULE}" # try to restore + fi + ${RMMOD} ${LOADED_MODULES} + exit 1 + fi + + LOADED_MODULES="${ECMODULE} ${LOADED_MODULES}" + done + + exit 0 + ;; + +#------------------------------------------------------------------------------ + +stop) + # unload EtherCAT device modules + for MODULE in ${DEVICE_MODULES} master; do + ECMODULE=ec_${MODULE} + if ! ${LSMOD} | grep -q "^${ECMODULE//-/_} "; then + continue # ec_* module not loaded + fi + if ! ${RMMOD} "${ECMODULE}"; then + exit 1 + fi; + done + + sleep 1 + + # load standard modules again + for MODULE in ${DEVICE_MODULES}; do + if [ "${MODULE}" == "generic" ] || [ "${MODULE}" == "ccat" ]; then + continue + fi + ${MODPROBE} ${MODPROBE_FLAGS} "${MODULE}" + done + + # bring down all updown interfaces + for interface in $UPDOWN_INTERFACES; do + $IP link set dev $interface down + done + + exit 0 + ;; + +#------------------------------------------------------------------------------ + +restart) + $0 stop || exit 1 + sleep 1 + $0 start + ;; + +#------------------------------------------------------------------------------ + +status) + echo "Checking for EtherCAT master 1.7.0 " + + # count masters in configuration file + MASTER_COUNT=0 + while true; do + DEVICE=$(eval echo "\${MASTER${MASTER_COUNT}_DEVICE}") + if [ -z "${DEVICE}" ]; then break; fi + MASTER_COUNT=$((${MASTER_COUNT} + 1)) + done + + RESULT=0 + + for i in $(seq 0 "$((${MASTER_COUNT} - 1))"); do + echo -n "Master${i} " + + # Check if the master is in idle or operation phase + ${ETHERCAT} master --master "${i}" 2>/dev/null | \ + grep -qE 'Phase:[[:space:]]*Idle|Phase:[[:space:]]*Operation' + EXITCODE=$? + + if [ ${EXITCODE} -eq 0 ]; then + echo " running" + else + echo " dead" + RESULT=1 + fi + done + + exit ${RESULT} + ;; + +#------------------------------------------------------------------------------ + +*) + echo "USAGE: $0 [-c path/to/ethercat.conf] {start|stop|restart|status}" + exit 1 + ;; +esac + +#------------------------------------------------------------------------------ diff --git a/dependency/x86/third_party/ethercat/v1.7.0/share/bash-completion/completions/ethercat b/dependency/x86/third_party/ethercat/v1.7.0/share/bash-completion/completions/ethercat new file mode 100644 index 00000000..3734b55b --- /dev/null +++ b/dependency/x86/third_party/ethercat/v1.7.0/share/bash-completion/completions/ethercat @@ -0,0 +1,80 @@ +# Copyright (C) 2022 Bjarne von Horn, Ingenieurgemeinschaft IgH +# +# This file is part of the IgH EtherCAT master userspace library. +# +# The IgH EtherCAT master userspace library is free software; you can +# redistribute it and/or modify it under the terms of the GNU Lesser General +# Public License as published by the Free Software Foundation; version 2.1 +# of the License. +# +# The IgH EtherCAT master userspace library is distributed in the hope that +# it will be useful, but WITHOUT ANY WARRANTY; without even the implied +# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with the IgH EtherCAT master userspace library. If not, see +# . +# + +_ethercat_completions() +{ + local ethercat_commands="alias config crc cstruct data debug domains download eoe foe_read foe_write graph master pdos reg_read reg_write rescan sdos sii_read sii_write slaves soe_read soe_write states upload version xml" + local options="--help --force --quiet --verbose --master " + if [ "$COMP_CWORD" -eq 1 ] ; then + COMPREPLY=($(compgen -W "$ethercat_commands --help" -- "${COMP_WORDS[1]}")) + elif [[ "${COMP_WORDS[1]}" != "--help" && ! "${COMP_WORDS[COMP_CWORD-1]}" =~ ^-a|-p|--alias|--position$ ]] ; then + case "${COMP_WORDS[1]}" in + "alias" | "config" | "cstruct" | "slaves" | "sdos" | "sii_read" | "upload" | "xml") + options+="--alias --position" + ;; + "crc") + options+="reset" + ;; + "debug") + options+="0 1 2" + ;; + "domains") + options+="--domain" + ;; + "download" | "reg_read" | "soe_read" | "soe_write") + if [[ "${COMP_WORDS[COMP_CWORD-1]}" =~ ^-t|--type$ ]] ; then + options="bool int8 int16 int32 int64 uint8 uint16 uint32 uint64 float double string octet_string unicode_string sm8 sm16 sm32 sm64" + else + options+="--alias --position --type" + fi + ;; + "foe_read" | "foe_write") + if [[ "${COMP_WORDS[COMP_CWORD-1]}" =~ ^-o|--output-file$ ]] ; then + COMPREPLY=($(compgen -o filenames -A file -- "${COMP_WORDS[$COMP_CWORD]}")) + else + options+="--alias --position --output-file" + COMPREPLY=($(compgen -o filenames -A file -W "$options" -- "${COMP_WORDS[$COMP_CWORD]}")) + fi + return + ;; + "graph") + options+="DC CRC" + ;; + "pdos") + if [[ "${COMP_WORDS[COMP_CWORD-1]}" =~ ^-s|--skin$ ]] ; then + options="default etherlab" + else + options+="--alias --position --skin" + fi + ;; + "sii_write") + options+="--alias --position" + COMPREPLY=($(compgen -o filenames -A file -W "$options" -- "${COMP_WORDS[$COMP_CWORD]}")) + return + ;; + "states") + options+="--alias --position INIT PREOP BOOT SAFEOP OP" + ;; + + esac + COMPREPLY+=($(compgen -W "$options" -- "${COMP_WORDS[$COMP_CWORD]}")) + fi +} + +complete -F _ethercat_completions ethercat