13#include <yarp/os/LogStream.h>
30constexpr uint32_t
mapEntry(uint16_t idx, uint8_t sub, uint8_t bits)
32 return (
static_cast<uint32_t
>(idx) << 16) | (
static_cast<uint32_t
>(sub) << 8) | bits;
40 if (m_watchThread.joinable())
62 auto wr8 = [
this, s](uint16_t idx, uint8_t sub, uint8_t v) {
63 return ecx_SDOwrite(&m_ctx, s, idx, sub, FALSE,
sizeof(v), &v, EC_TIMEOUTRXM);
65 auto wr16 = [
this, s](uint16_t idx, uint8_t sub, uint16_t v) {
66 return ecx_SDOwrite(&m_ctx, s, idx, sub, FALSE,
sizeof(v), &v, EC_TIMEOUTRXM);
68 auto wr32 = [
this, s](uint16_t idx, uint8_t sub, uint32_t v) {
69 return ecx_SDOwrite(&m_ctx, s, idx, sub, FALSE,
sizeof(v), &v, EC_TIMEOUTRXM);
80 for (uint16_t p = 0x1600; p <= 0x160F; ++p)
84 for (uint16_t p = 0x1A00; p <= 0x1A0F; ++p)
93 wr32(0x1600, ++rx_n,
mapEntry(0x6040, 0x00, 16));
94 wr32(0x1600, ++rx_n,
mapEntry(0x6060, 0x00, 8));
95 wr32(0x1600, ++rx_n,
mapEntry(0x6071, 0x00, 16));
96 wr32(0x1600, ++rx_n,
mapEntry(0x607A, 0x00, 32));
97 wr32(0x1600, ++rx_n,
mapEntry(0x60FF, 0x00, 32));
100 wr8(0x1600, 0x00, rx_n);
109 constexpr int MAX_ENTRIES_PER_PDO = 8;
111 uint32_t byteOff = 0;
112 m_txMap[s - 1].clear();
115 uint16_t curTxIdx = 0x1A00;
116 uint8_t curCount = 0;
117 std::vector<uint16_t> txUsed;
121 auto beginTx = [&]() {
123 if (wr8(curTxIdx, 0x00, 0) <= 0)
130 auto finalizeTx = [&]() {
return wr8(curTxIdx, 0x00, curCount) > 0; };
133 auto nextTx = [&]() {
141 auto ensureCap = [&]() {
142 if (curCount >= MAX_ENTRIES_PER_PDO)
152 auto tryMap = [&](
TxField id, uint16_t idx, uint8_t sub, uint8_t bits) {
157 if (wr32(curTxIdx, ++curCount,
mapEntry(idx, sub, bits)) <= 0)
166 m_txMap[s - 1][id] = FieldInfo{id, byteOff, bits};
218 wr16(0x1C12, 1, 0x1600);
219 wr8(0x1C12, 0x00, 1);
223 wr8(0x1C13, 0x00, 0);
224 for (
size_t k = 0; k < txUsed.
size(); ++k)
225 wr16(0x1C13,
static_cast<uint8_t
>(k + 1), txUsed[k]);
226 wr8(0x1C13, 0x00,
static_cast<uint8_t
>(txUsed.
size()));
229 yCDebug(CIA402,
"Slave %d '%s': configured %d RxPDOs, %d TxPDOs (%d bytes input)",
231 m_ctx.slavelist[s].name,
233 static_cast<int>(txUsed.
size()),
234 static_cast<int>(byteOff));
251 yCInfo(CIA402,
"%s: EtherCAT: init on %s", m_kClassName.data(), ifname.c_str());
252 if (ecx_init(&m_ctx, ifname.c_str()) <= 0)
254 yCError(CIA402,
"%s: cannot initialize on %s", m_kClassName.data(), ifname.c_str());
263 if (ecx_config_init(&m_ctx) <= 0)
269 yCInfo(CIA402,
"%s: found %d slaves", m_kClassName.data(), (m_ctx.slavecount ? m_ctx.slavecount : 0));
276 for (
int s = 1; s <= m_ctx.slavecount; ++s)
279 ecx_statecheck(&m_ctx, s, EC_STATE_PRE_OP, EC_TIMEOUTSTATE);
281 if (!(m_ctx.slavelist[s].mbx_proto & ECT_MBXPROT_COE))
283 yCError(CIA402,
"%s: Slave %d '%s' has no CoE mailbox → cannot use SDO",
286 m_ctx.slavelist[s].name);
291 if (!(m_ctx.slavelist[s].CoEdetails & ECT_COEDET_SDO))
293 yCError(CIA402,
"%s: Slave %d '%s' has no SDO support",
296 m_ctx.slavelist[s].name);
303 m_rxPtr.assign(m_ctx.slavecount,
nullptr);
304 m_txRaw.assign(m_ctx.slavecount,
nullptr);
305 m_txMap.assign(m_ctx.slavecount, {});
310 for (
int s = 1; s <= m_ctx.slavecount; ++s)
312 yCInfo(CIA402,
"%s: configuring slave %d: %s", m_kClassName.data(), s, m_ctx.slavelist[s].name);
323 if (ecx_config_map_group(&m_ctx, m_ioMap, 0) <= 0)
329 ecx_configdc(&m_ctx);
342 m_ctx.slavelist[ALL].state = EC_STATE_SAFE_OP;
343 ecx_writestate(&m_ctx, ALL);
344 ecx_statecheck(&m_ctx, ALL, EC_STATE_SAFE_OP, EC_TIMEOUTSTATE * 4);
346 if (m_ctx.slavelist[ALL].state != EC_STATE_SAFE_OP)
348 yCError(CIA402,
"%s: Ring failed to reach SAFE-OP (AL-status 0x%04x)",
350 m_ctx.slavelist[ALL].ALstatuscode);
357 ecx_send_processdata(&m_ctx);
358 ecx_receive_processdata(&m_ctx, EC_TIMEOUTRET);
365 for (
int s = 1; s <= m_ctx.slavecount; ++s)
367 m_rxPtr[s - 1] =
reinterpret_cast<RxPDO*
>(m_ctx.slavelist[s].outputs);
368 m_txRaw[s - 1] =
reinterpret_cast<uint8_t*
>(m_ctx.slavelist[s].inputs);
372 m_initialized =
true;
373 m_isOperational =
false;
375 yCInfo(CIA402,
"%s: EtherCAT: ring is SAFE-OP (waiting for goOperational)", m_kClassName.data());
386 constexpr int kImmediateRetries = 3;
393 auto sendReceiveOnce = [&]() {
394 ecx_send_processdata(&m_ctx);
395 m_lastWkc = ecx_receive_processdata(&m_ctx, m_pdoTimeoutUs);
398 for (
int attempt = 0; attempt < kImmediateRetries; ++attempt)
402 if (!m_isOperational)
408 if (m_lastWkc >= m_expectedWkc)
410 m_consecutivePdoErrors = 0;
418 ++m_consecutivePdoErrors;
422 constexpr int kRecoveryThreshold = 2;
423 if (m_consecutivePdoErrors >= kRecoveryThreshold)
425 m_ctx.grouplist[0].docheckstate = 1;
427 "%s: sendReceive: WKC=%d expected=%d (consecutive=%d) → scheduling recovery",
431 m_consecutivePdoErrors);
447 m_ctx.slavelist[ALL].state = EC_STATE_OPERATIONAL;
448 ecx_writestate(&m_ctx, ALL);
453 const int pollTimeout = 50'000;
456 ecx_statecheck(&m_ctx, ALL, EC_STATE_OPERATIONAL, pollTimeout);
457 }
while (attempts-- && m_ctx.slavelist[ALL].state != EC_STATE_OPERATIONAL);
459 if (m_ctx.slavelist[ALL].state != EC_STATE_OPERATIONAL)
461 yCError(CIA402,
"%s: Ring failed to reach OP (AL-status 0x%04x)",
463 m_ctx.slavelist[ALL].ALstatuscode);
468 m_expectedWkc = m_ctx.grouplist[0].outputsWKC * 2 + m_ctx.grouplist[0].inputsWKC;
472 m_watchThread =
std::thread(&EthercatManager::errorMonitorLoop,
this);
473 m_isOperational =
true;
474 m_consecutivePdoErrors = 0;
475 yCInfo(CIA402,
"%s: EtherCAT: ring is OPERATIONAL", m_kClassName.data());
486 if (m_watchThread.joinable())
487 m_watchThread.join();
495 m_ctx.slavelist[ALL].state = EC_STATE_PRE_OP;
496 ecx_writestate(&m_ctx, ALL);
501 const int pollTimeout = 50'000;
504 ecx_statecheck(&m_ctx, ALL, EC_STATE_PRE_OP, pollTimeout);
505 }
while (attempts-- && m_ctx.slavelist[ALL].state != EC_STATE_PRE_OP);
507 if (m_ctx.slavelist[ALL].state != EC_STATE_PRE_OP)
509 yCError(CIA402,
"%s: Ring failed to reach PRE-OP (AL-status 0x%04x)",
511 m_ctx.slavelist[ALL].ALstatuscode);
515 m_isOperational =
false;
516 m_consecutivePdoErrors = 0;
517 yCInfo(CIA402,
"%s: EtherCAT: ring is PRE-OP", m_kClassName.data());
524 yCError(CIA402,
"%s: WKC=%d expected=%d, slavecount=%d",
531 ecx_readstate(&m_ctx);
532 for (
int s = 1; s <= m_ctx.slavecount; ++s)
534 const ec_slavet& sl = m_ctx.slavelist[s];
536 int sm2 = (EC_MAXSM > 3) ? sl.SM[2].StartAddr : -1;
537 int sm3 = (EC_MAXSM > 3) ? sl.SM[3].StartAddr : -1;
538 yCError(CIA402,
"%s: s%02d '%s' state=0x%02X AL=0x%04X SM2=0x%04X SM3=0x%04X",
551 return this->indexValid(slaveIdx) ? m_rxPtr[slaveIdx - 1] :
nullptr;
556 return this->indexValid(slaveIdx) ? m_rxPtr[slaveIdx - 1] :
nullptr;
561 return this->indexValid(slaveIdx) ?
TxView(m_txRaw[slaveIdx - 1], &m_txMap[slaveIdx - 1])
562 :
TxView(
nullptr,
nullptr);
565void EthercatManager::errorMonitorLoop() noexcept
567 using namespace std::chrono_literals;
568 constexpr auto baseSleep = 50ms;
569 constexpr auto maxSleep = 500ms;
570 auto sleepDuration = baseSleep;
572 while (m_runWatch.
load())
574 bool shouldCheck =
false;
577 shouldCheck = m_isOperational
578 && ((m_lastWkc < m_expectedWkc)
579 || m_ctx.grouplist[0].docheckstate != 0);
588 bool actionsPerformed =
false;
589 bool recoveredAny =
false;
592 std::lock_guard<std::mutex> lk(m_ioMtx);
594 ecx_readstate(&m_ctx);
596 for (
int s = 1; s <= m_ctx.slavecount; ++s)
598 ec_slavet& sl = m_ctx.slavelist[s];
600 if (sl.state == EC_STATE_OPERATIONAL)
606 actionsPerformed =
true;
609 "%s: monitor: slave %d '%s' state=0x%02X AL=0x%04X islost=%d",
617 if ((sl.state & EC_STATE_ERROR) != 0)
619 sl.state = EC_STATE_SAFE_OP + EC_STATE_ACK;
620 ecx_writestate(&m_ctx, s);
621 ecx_statecheck(&m_ctx, s, EC_STATE_SAFE_OP, EC_TIMEOUTSTATE);
624 if (sl.state == EC_STATE_SAFE_OP)
626 sl.state = EC_STATE_OPERATIONAL;
627 ecx_writestate(&m_ctx, s);
628 ecx_statecheck(&m_ctx, s, EC_STATE_OPERATIONAL, EC_TIMEOUTSTATE);
631 if (sl.state != EC_STATE_OPERATIONAL)
633 if (sl.state == EC_STATE_NONE)
642 "%s: monitor: attempting recover on slave %d '%s'",
646 rc = ecx_recover_slave(&m_ctx,
static_cast<uint16
>(s), EC_TIMEOUTRET3);
651 "%s: monitor: attempting reconfig on slave %d '%s'",
655 rc = ecx_reconfig_slave(&m_ctx,
static_cast<uint16
>(s), EC_TIMEOUTRET3);
661 sl.state = EC_STATE_OPERATIONAL;
662 ecx_writestate(&m_ctx, s);
663 ecx_statecheck(&m_ctx, s, EC_STATE_OPERATIONAL, EC_TIMEOUTSTATE);
671 if (sl.state == EC_STATE_OPERATIONAL)
675 "%s: monitor: slave %d '%s' restored to OP",
683 "%s: monitor: slave %d '%s' still not OP (state=0x%02X AL=0x%04X)",
692 if (actionsPerformed)
694 ecx_send_processdata(&m_ctx);
695 m_lastWkc = ecx_receive_processdata(&m_ctx, m_pdoTimeoutUs);
696 if (m_lastWkc >= m_expectedWkc)
702 if (m_ctx.grouplist[0].docheckstate)
704 m_ctx.grouplist[0].docheckstate = 0;
710 sleepDuration = baseSleep;
712 else if (actionsPerformed)
714 sleepDuration =
std::min(sleepDuration * 2, maxSleep);
718 sleepDuration = baseSleep;
727 if (!indexValid(slaveIndex))
731 return m_ctx.slavelist[slaveIndex].name;
734bool EthercatManager::indexValid(
int slaveIndex)
const noexcept
736 return slaveIndex >= 1 && slaveIndex <= m_ctx.slavecount;
750 for (
int s = 1; s <= m_ctx.slavecount; ++s)
754 ecx_dcsync0(&m_ctx, s,
true, cycleNs, shiftNs);
766 for (
int s = 1; s <= m_ctx.slavecount; ++s)
768 ecx_dcsync0(&m_ctx, s,
false, 0, 0);
constexpr uint32_t mapEntry(uint16_t idx, uint8_t sub, uint8_t bits)
Helper function to create a PDO mapping entry.
TxView getTxView(int slaveIndex) const noexcept
Obtain a typed, bounds-checked view over the TxPDO (slave→master).
~EthercatManager()
Destructor, stops background monitoring and releases resources.
Error goPreOp() noexcept
Transition all slaves back to PRE-OP and stop monitoring.
EthercatManager()
Construct the manager (no I/O started).
Error enableDCSync0(uint32_t cycleNs, int32_t shiftNs=0) noexcept
Enable DC Sync0 for a slave.
Error init(const std::string &ifname) noexcept
Initialize the EtherCAT stack and configure slaves.
std::string getName(int slaveIndex) const noexcept
Get the name of a slave device.
Error sendReceive() noexcept
Perform one cyclic send/receive exchange over the bus.
Error goOperational() noexcept
Transition all slaves to OPERATIONAL and start monitoring.
void dumpDiagnostics() noexcept
Print quick diagnostics about bus/slave state and WKC. Call this when a PDO exchange fails to get imm...
const RxPDO * getRxPDO(int slaveIndex) const noexcept
Access the RxPDO (master→slave) buffer for a given slave.
Error disableDCSync0() noexcept
Disable DC Sync0 for a slave.
Error
Error codes returned by EthercatManager APIs.
@ PdoExchangeFailed
Failed to exchange PDOs or read SDO.
@ SlavesNotOp
Slaves did not reach OP state.
@ ConfigFailed
PDO/SDO configuration failed.
@ InitFailed
Bus/stack initialization failed.
@ NoError
Operation completed successfully.
@ NotInitialized
Operation requires a prior successful init().
@ AlreadyInitialized
init() called more than once.
@ NoSlavesFound
No EtherCAT slaves were discovered.
Lightweight view over a slave's TxPDO image.
TxField
Identifiers for fields read from a slave (TxPDO → master).
@ Velocity606C
0x606C: Velocity actual value
@ Enc2Pos2113_02
0x2113:02 Vendor enc2 position
@ SBC_6621_02
0x6621:02 Safe Brake Control status (exposed via PDO)
@ Enc2Vel2113_03
0x2113:03 Vendor enc2 velocity
@ OpModeDisplay
0x6061: Modes of operation display
@ Timestamp20F0
0x20F0: Timestamp (if supported)
@ PositionError6065
0x6065: Position demand value / error
@ Statusword
0x6041: Statusword
@ TemperatureDrive
0x2031:01 Temperature (if supported)
@ Position6064
0x6064: Position actual value
@ Torque6077
0x6077: Torque actual value
@ Enc1Pos2111_02
0x2111:02 Vendor enc1 position
@ Enc1Vel2111_03
0x2111:03 Vendor enc1 velocity
@ STO_6621_01
0x6621:01 Safe Torque Off status (exposed via PDO)
Process Data Object written from master to slave (RxPDO).