feat(ethercat): add motor bus runtime

This commit is contained in:
lgv 2026-07-09 14:20:23 +08:00
parent d292360a8d
commit c9c7e43a70
26 changed files with 5066 additions and 15 deletions

View File

@ -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
)

View File

@ -1,10 +1,22 @@
#ifndef CMVR_ES_ETHERCAT_MOTOR_BUS_RUNTIME_H
#define CMVR_ES_ETHERCAT_MOTOR_BUS_RUNTIME_H
#include <atomic>
#include <cstdint>
#include <cstring>
#include <mutex>
#include <string>
#include <thread>
#include <type_traits>
#include <unordered_map>
#include <vector>
#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 <typename T>
bool writePdo(int motor_id, std::uint16_t index, std::uint8_t subindex, T value)
{
return writePdoRaw_(motor_id, index, subindex, valueBitLength_<T>(),
toRawValue_(value));
}
template <typename T>
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_<T>(), raw)) {
return false;
}
value = fromRawValue_<T>(raw);
return true;
}
template <typename T>
bool writeSdo(int motor_id, std::uint16_t index, std::uint8_t subindex, T value)
{
return writeSdoRaw_(motor_id, index, subindex, valueBitLength_<T>(),
toRawValue_(value));
}
template <typename T>
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_<T>(), raw)) {
return false;
}
value = fromRawValue_<T>(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<std::uint32_t, PdoEntryRuntime> 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 <typename T>
static constexpr std::uint8_t valueBitLength_()
{
using ValueType = std::remove_cv_t<T>;
static_assert(std::is_integral_v<ValueType>, "EtherCAT object values must be integral");
static_assert(!std::is_same_v<ValueType, bool>, "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<std::uint8_t>(sizeof(ValueType) * 8);
}
template <typename T>
static std::uint64_t toRawValue_(T value)
{
using ValueType = std::remove_cv_t<T>;
using UnsignedType = std::make_unsigned_t<ValueType>;
return static_cast<std::uint64_t>(static_cast<UnsignedType>(value));
}
template <typename T>
static T fromRawValue_(std::uint64_t raw)
{
using ValueType = std::remove_cv_t<T>;
using UnsignedType = std::make_unsigned_t<ValueType>;
const auto unsigned_value = static_cast<UnsignedType>(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<int, const config::EthercatSlaveConfig*> slaves_by_motor_id_;
EthercatPdoMapping pdo_mapping_;
std::unordered_map<int, SlaveRuntime> 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<bool> running_{false};
bool initialized_{false};
bool started_{false};
};

View File

@ -0,0 +1,35 @@
#ifndef CMVR_ES_ETHERCAT_PDO_MAPPING_H
#define CMVR_ES_ETHERCAT_PDO_MAPPING_H
#include <cstdint>
#include <string>
#include <vector>
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<EthercatPdoEntryConfig> entries;
};
struct EthercatPdoMapping {
std::uint32_t vendor_id{0};
std::uint32_t product_code{0};
std::string name;
std::vector<EthercatPdoConfig> rx_pdos;
std::vector<EthercatPdoConfig> tx_pdos;
};
} // namespace cmvr::device
#endif // CMVR_ES_ETHERCAT_PDO_MAPPING_H

View File

@ -1,7 +1,17 @@
#include "motor/bus_runtime/ethercat/include/ethercat_motor_bus_runtime.h"
#include <algorithm>
#include <array>
#include <chrono>
#include <cstddef>
#include <map>
#include <sstream>
#include <utility>
#include "common/base/logging/logger.h"
#include <ecrt.h>
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<std::mutex> 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<std::mutex> 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<std::uint16_t>(slave.cfg.alias()),
static_cast<std::uint16_t>(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<std::uint16_t> pdo_indices;
std::vector<std::vector<ec_pdo_entry_info_t>> entry_storage;
std::vector<ec_pdo_info_t> pdo_infos;
};
auto add_pdo_to_sync_build = [](std::map<std::uint8_t, SyncBuild>& 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<std::uint8_t, SyncBuild> 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<unsigned int>(entries.size()),
entries.data(),
});
}
}
std::vector<ec_sync_info_t> 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<unsigned int>(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<int>(entry.bit_len)
<< ", entry=" << hexIndex_(entry.index)
<< ":" << static_cast<int>(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<int>(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<int>(entry.subindex)
<< ", motor_id=" << slave.cfg.motor_id()
<< ", group=" << id_;
return false;
}
PdoEntryRuntime runtime;
runtime.cfg = entry;
runtime.offset = static_cast<unsigned int>(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<std::mutex> 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<std::mutex> 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<std::mutex> 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<int>(bit_len)
<< ", object=" << hexIndex_(index)
<< ":" << static_cast<int>(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<int>(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<int>(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<std::uint8_t>(raw_value));
break;
case 16:
result = ecrt_slave_config_sdo16(slave.slave_config, index, subindex,
static_cast<std::uint16_t>(raw_value));
break;
case 32:
result = ecrt_slave_config_sdo32(slave.slave_config, index, subindex,
static_cast<std::uint32_t>(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<int>(subindex)
<< ", bit_len=" << static_cast<int>(bit_len)
<< ", value=" << raw_value
<< ", result=" << result;
return false;
}
return true;
}
std::array<std::uint8_t, 4> data{};
switch (bit_len) {
case 8:
EC_WRITE_U8(data.data(), static_cast<std::uint8_t>(raw_value));
break;
case 16:
EC_WRITE_U16(data.data(), static_cast<std::uint16_t>(raw_value));
break;
case 32:
EC_WRITE_U32(data.data(), static_cast<std::uint32_t>(raw_value));
break;
default:
break;
}
const auto data_size = static_cast<std::size_t>(bit_len / 8);
std::uint32_t abort_code = 0;
const int result = ecrt_master_sdo_download(
master_,
static_cast<std::uint16_t>(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<int>(subindex)
<< ", bit_len=" << static_cast<int>(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<int>(bit_len)
<< ", object=" << hexIndex_(index)
<< ":" << static_cast<int>(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<int>(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<int>(subindex);
return false;
}
std::array<std::uint8_t, 4> data{};
const auto data_size = static_cast<std::size_t>(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<std::uint16_t>(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<int>(subindex)
<< ", bit_len=" << static_cast<int>(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<std::uint32_t>(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<std::uint8_t>(entry.value));
break;
case 16:
EC_WRITE_U16(domain_data + entry.offset, static_cast<std::uint16_t>(entry.value));
break;
case 32:
EC_WRITE_U32(domain_data + entry.offset, static_cast<std::uint32_t>(entry.value));
break;
default:
break;
}
}
} // namespace cmvr::device

View File

@ -0,0 +1,91 @@
#include "devices/motor/bus_runtime/ethercat/include/ethercat_motor_bus_runtime.h"
#include <algorithm>
#include <chrono>
#include <cstdint>
#include <iostream>
#include <string>
#include <thread>
#include <gtest/gtest.h>
#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<std::uint16_t>(1, msgs::CIA402_CONTROL_WORD_6040, 0x00, 0x0000));
EXPECT_TRUE(runtime.writePdo<std::int8_t>(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<std::uint16_t>(1, msgs::CIA402_STATUS_WORD_6041, 0x00, statusword));
std::int8_t mode_display = 0;
EXPECT_TRUE(runtime.readPdo<std::int8_t>(1, msgs::CIA402_MODE_DISPLAY_6061, 0x00, mode_display));
std::cout << "CIA402 statusword: 0x" << std::hex << statusword
<< ", mode display: " << std::dec << static_cast<int>(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

Binary file not shown.

View File

@ -0,0 +1,3 @@
MASTER0_DEVICE="a0:ad:9f:c4:c2:2c"
DEVICE_MODULES="generic"
UPDOWN_INTERFACES="eno1"

View File

@ -0,0 +1,113 @@
#------------------------------------------------------------------------------
#
# EtherCAT master configuration file for use with ethercatctl.
#
# vim: spelllang=en spell tw=78
#
#------------------------------------------------------------------------------
#
# Main Ethernet devices.
#
# The MASTER<X>_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<X>_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<X>_BACKUP variables specify the devices used for redundancy. They
# behaves nearly the same as the MASTER<X>_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-<driver> 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"
#------------------------------------------------------------------------------

View File

@ -0,0 +1,3 @@
MASTER0_DEVICE="a0:ad:9f:c4:c2:2c"
DEVICE_MODULES="generic"
UPDOWN_INTERFACES="eno1"

View File

@ -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
#------------------------------------------------------------------------------

View File

@ -0,0 +1,113 @@
#------------------------------------------------------------------------------
#
# EtherCAT master configuration file for use with init.d.
#
# vim: spelllang=en spell tw=78
#
#------------------------------------------------------------------------------
#
# Main Ethernet devices.
#
# The MASTER<X>_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<X>_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<X>_BACKUP variables specify the devices used for redundancy. They
# behaves nearly the same as the MASTER<X>_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-<driver> 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"
#------------------------------------------------------------------------------

File diff suppressed because it is too large Load Diff

View File

@ -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
* <http://www.gnu.org/licenses/>.
*
****************************************************************************/
/** \file
*
* EtherCAT virtual TTY interface.
*
* \defgroup TTYInterface EtherCAT Virtual TTY Interface
*
* @{
*/
/****************************************************************************/
#ifndef __ECTTY_H__
#define __ECTTY_H__
#include <linux/termios.h>
/*****************************************************************************
* 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

View File

@ -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()

Binary file not shown.

View File

@ -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'

View File

@ -0,0 +1 @@
libethercat.so.1.2.0

View File

@ -0,0 +1 @@
libethercat.so.1.2.0

Binary file not shown.

View File

@ -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 <http://www.gnu.org/licenses/>.
#
# 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}

View File

@ -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

View File

@ -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
#------------------------------------------------------------------------------

View File

@ -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
# <http://www.gnu.org/licenses/>.
#
_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