BusIO.cpp
Go to the documentation of this file.
1 #include "BusIO.h"
2 
3 #include <ethercat.h>
4 
7 
9 
13 
14 #include "EtherCATFrame.h"
20 
22 {
23  static constexpr int TIMEOUT_UPDATE_PDO_US = 600;
24 
26  {
27  //writing zeros to the IOMap
28  ioMap.fill(0);
29 
30  requestHandlerThread = std::thread(&BusIO::requestHandlerLoop, this);
31  }
32 
33  bool
34  BusIO::generalSDOWrite(std::uint16_t slaveIndex,
35  std::uint16_t index,
36  std::uint8_t subindex,
37  std::uint16_t buflen,
38  const void* buf,
39  bool completeAccess)
40  {
41  return requestQueues.postSDOWriteRequest(
42  {slaveIndex, index, subindex},
43  buflen,
44  static_cast<unsigned char*>(const_cast<void*>(buf)),
45  completeAccess);
46  }
47 
48  bool
49  BusIO::generalSDORead(std::uint16_t slaveIndex,
50  std::uint16_t index,
51  std::uint8_t subindex,
52  int* buflen,
53  void* buf,
54  bool completeAccess)
55  {
56  return requestQueues.postSDOReadRequest({slaveIndex, index, subindex},
57  buflen,
58  static_cast<unsigned char*>(buf),
59  completeAccess);
60  }
61 
62  bool
63  BusIO::isSDOAccessAvailable() const
64  {
65  if (!sdoAccessAvailable)
66  {
68  "SDO-access is not yet available. Probably the bus was not started yet");
69  return false;
70  }
71  return true;
72  }
73 
74  std::pair<int, int>
75  BusIO::sendAndReceiveEtherCATFrame(const EtherCATFrame* frame,
76  size_t frameLength,
77  uint16_t slaveConfiguredAddress,
78  const std::vector<size_t>& slaveAddressOffsets)
79  {
80  int bufferIndex = ecx_getindex(ecx_context.port);
81  uint8_t* buffer =
82  reinterpret_cast<uint8_t*>(&(ecx_context.port->txbuf[bufferIndex])); // NOLINT
83 
84  // The Ethernet header will already be placed in the buffer, so skip it
85  EtherCATFrame* framePlacement =
86  reinterpret_cast<EtherCATFrame*>(buffer + ETH_HEADERSIZE); // NOLINT
87  std::memcpy(framePlacement, frame, frameLength);
88 
89  framePlacement->pduArea[1] = static_cast<std::uint8_t>(bufferIndex);
90  for (size_t offset : slaveAddressOffsets)
91  {
92  *reinterpret_cast<uint16_t*>(framePlacement->pduArea + offset) // NOLINT
94  }
95 
96  ecx_context.port->txbuflength[bufferIndex] =
97  static_cast<int>(ETH_HEADERSIZE + frameLength); // NOLINT
98 
99  // This is blocking, which seems good enough for now.
100  int const workingCounter =
101  ecx_srconfirm(ecx_context.port, bufferIndex, timeouts.registerRead_us);
102 
103  if (workingCounter == EC_NOFRAME)
104  {
106  "Sending and receiving EtherCAT frame timed out after %i µs (EC_NOFRAME). "
107  "This timeout will most likely result in consecutive errors.",
109  }
110 
111  return {workingCounter, bufferIndex};
112  }
113 
114  void
116  {
117  GENERAL_TIMING_START(updatePDOtiming)
118 
119  pdoUpdateRequested.store(true, std::memory_order_relaxed);
120 
121  GENERAL_TIMING_START(send_processdata)
122  ec_send_processdata();
123  GENERAL_TIMING_CEND(send_processdata, TIMEOUT_UPDATE_PDO_US / 1000.0 + 0.05)
124  GENERAL_TIMING_START(receive_processdata);
125  lastWorkCounter = ec_receive_processdata(TIMEOUT_UPDATE_PDO_US);
126  GENERAL_TIMING_CEND(receive_processdata, TIMEOUT_UPDATE_PDO_US / 1000.0 + 0.05)
127  // Request Handler can continue to run
128  pdoUpdateRequested.store(false, std::memory_order_relaxed);
129 
130  GENERAL_TIMING_CEND(updatePDOtiming, TIMEOUT_UPDATE_PDO_US / 1000.0 + 0.05)
131  }
132 
133  void
134  BusIO::requestHandlerLoop()
135  {
138 
139  while (requestHandlerThreadRunning.load() == true)
140  {
141  if (!pdoUpdateRequested.load(std::memory_order_relaxed))
142  {
143  handleNextRequest();
144  }
145  using namespace std::literals;
146  std::this_thread::sleep_for(10us);
147  }
148  }
149 
150  bool
151  BusIO::handleNextRequest()
152  {
153  auto request = requestQueues.getNextRequest();
154  if (request)
155  {
156  TIMING_START(requestDuration)
157  if (auto r = std::dynamic_pointer_cast<SDOUpdateRequest>(request))
158  {
159  handleSDOUpdateRequest(r);
160  TIMING_CEND_COMMENT(requestDuration, "SDOUpdateRequest", 5)
161  }
162  else if (auto r = std::dynamic_pointer_cast<ChangeStateRequest>(request))
163  {
164  handleChangeStateRequest(r);
165  TIMING_CEND_COMMENT(requestDuration, "ChangeStateRequest", 1)
166  }
167  else if (auto r = std::dynamic_pointer_cast<ReadStatesRequest>(request))
168  {
169  handleReadStatesRequest(r);
170  TIMING_CEND_COMMENT(requestDuration, "ReadStatesRequest", 1)
171  }
172  else if (auto r = std::dynamic_pointer_cast<RegisterResetRequest>(request))
173  {
174  handleRegisterResetRequest(r);
175  TIMING_CEND_COMMENT(requestDuration, "RegisterResetRequest", 1)
176  }
177  else if (auto r = std::dynamic_pointer_cast<RegisterReadRequest>(request))
178  {
179  handleRegisterReadRequest(r);
180  TIMING_CEND_COMMENT(requestDuration, "RegisterReadRequest", 1)
181  }
182 
183  requestQueues.postReply(std::move(request));
184 
185  return true;
186  }
187  return false;
188  }
189 
190  void
191  BusIO::handleChangeStateRequest(const std::shared_ptr<ChangeStateRequest>& request)
192  {
193  ec_slave[request->slaveIndex].state = request->state;
194  int ret = ec_writestate(request->slaveIndex);
195  if (ret == EC_NOFRAME)
196  {
198  "Writing EtherCAT state %s to slave at index %u (0 = all slaves) timed out "
199  "(EC_NOFRAME). The timeout for this operation was %i µs, is hardcoded by "
200  "SOEM, and thus cannot be configured.",
201  request->slaveIndex,
202  request->state.c_str(),
203  /* Timeout for SOEM function ecx_writestate(...) = */ EC_TIMEOUTRET3);
204 
205  request->setFailed();
206  }
207  else
208  {
209  if (request->validate)
210  {
211  auto const start = armarx::rtNow();
212  EtherCATState actualState =
213  ec_statecheck(request->slaveIndex, request->state, timeouts.stateCheck_us);
214  auto const statecheckDuration = armarx::rtNow() - start;
215 
216  if (actualState != request->state)
217  {
218  // We can only guess whether the timeout was reached or not, but it is very
219  // likely that it reached the timout if it took longer than it should (all
220  // overhead considered).
221  bool const probablyTimedOut =
222  statecheckDuration.toMicroSeconds() > timeouts.stateCheck_us;
223 
225  "Could not validate EtherCAT state of slave at index %u (0 = all slaves).\n"
226  "Requested state is %s and the actual state is %s.\n"
227  "If the SOEM operation to check a slave's state timed out, the actual "
228  "state might just be the minimum state SOEM was able to derive within that "
229  "timeout.\n"
230  "The configured timeout is %i µs and the operation took %i µs%s.",
231  request->slaveIndex,
232  request->state.c_str(),
233  actualState.c_str(),
235  statecheckDuration.toMicroSeconds(),
236  probablyTimedOut ? ", so the operation probably timed out" : "");
237  request->setFailed();
238  }
239 
240  request->setActualState(actualState);
241  }
242  }
243  }
244 
245  void
246  BusIO::handleReadStatesRequest(const std::shared_ptr<ReadStatesRequest>& request)
247  {
248  EtherCATState state = static_cast<std::uint16_t>(ec_readstate());
249  request->setState(state);
250  }
251 
252  void
253  BusIO::handleRegisterResetRequest(const std::shared_ptr<RegisterResetRequest>& request)
254  {
255  static constexpr size_t lengthOfPreMadeFrame = 44;
256  static const std::vector<size_t> slaveAddressOffsets{2, 28};
257 
258  static constexpr EtherCATFrame preMadeResetFrame{
259  (lengthOfPreMadeFrame - 2) | 0x1000 /* Length of frame - 2 | PDU type */,
260  {
261  // clang-format off
262  0x05 /* FPWR */, 0xff /* Index */, 0xff, 0xff /* Slave address */,
263  0x00, 0x03 /* Error counters' address */, 0x0e, 0x00 /* Error counters' length */,
264  0x00, 0x00 /* External event */, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
265  0x00, 0x00, 0x00, 0x00, 0x00, 0x00 /* Data area */, 0x00, 0x00 /* Working counter */,
266 
267  // There's a gap in the registers here that can't be written to,
268  // so we have to split the frame into two PDUs.
269 
270  0x05 /* FPWR */, 0xff /* Index */, 0xff, 0xff /* Slave address */,
271  0x10, 0x03 /* Error counters' address */, 0x04, 0x00 /* Error counters' length */,
272  0x00, 0x00 /* External event */, 0x00, 0x00, 0x00, 0x00 /* Data area */,
273  0x00, 0x00 /* Working counter */
274  // clang-format on
275  }};
276 
277  auto [workingCounter, bufferIndex] =
278  sendAndReceiveEtherCATFrame(&preMadeResetFrame,
279  lengthOfPreMadeFrame,
280  ec_slave[request->slaveIndex].configadr,
281  slaveAddressOffsets);
282 
283  if (workingCounter == EC_NOFRAME)
284  {
285  GENERAL_ERROR("Failed to reset error counters for slave at index %u.",
286  request->slaveIndex);
287  request->setFailed();
288  }
289 
290  ecx_setbufstat(ecx_context.port, bufferIndex, EC_BUF_EMPTY);
291  }
292 
293  void
294  BusIO::handleRegisterReadRequest(const std::shared_ptr<RegisterReadRequest>& request)
295  {
296  auto [frameList, count] = request->getFrames();
297 
298  for (EtherCATFrameIterator it =
299  EtherCATFrameIterator(frameList, frameList->nextIndex, count);
300  !it.atEnd();
301  ++it)
302  {
303  auto [frame, metaData] = *it;
304  auto [workingCounter, bufferIndex] =
305  sendAndReceiveEtherCATFrame(frame, metaData->lengthOfFrame, 0, {});
306  if (workingCounter != EC_NOFRAME)
307  {
308  // SOEM strips the Ethernet header for us in rxbuf, so we don't need
309  // to account for it.
310  std::memcpy(frame,
311  reinterpret_cast<EtherCATFrame*>(
312  &(ecx_context.port->rxbuf[bufferIndex])), // NOLINT
313  metaData->lengthOfFrame);
314  }
315  else
316  {
317  std::stringstream ss;
318  ss << "EtherCATFrame containing requests for reading registers from following "
319  "slaves returned with EC_NOFRAME:\n";
320  for (const auto& pdu : metaData->pdus)
321  {
322  std::uint16_t slaveIndex = 0;
323  for (int i = 1; i <= ec_slavecount; i++)
324  {
325  if (ec_slave[i].configadr == pdu.slaveConfiguredAddress)
326  {
327  slaveIndex = static_cast<std::uint16_t>(i);
328  break;
329  }
330  }
331 
332  ss << std::to_string(slaveIndex) << ", ";
333  }
334  ss.seekp(ss.str().length() - 2);
335  ss << "\n This happened on frame " << it.getCurrentIndex() << " out of " << count
336  << " requested Frames. \n";
337  ss.seekp(ss.str().length() - 2);
338  GENERAL_ERROR("%s", ss.str().c_str());
339 
340  request->setFailed();
341  }
342  ecx_setbufstat(ecx_context.port, bufferIndex, EC_BUF_EMPTY);
343  }
344 
345  request->updateRequestedRegisters();
346  }
347 
348  void
349  BusIO::handleSDOUpdateRequest(const std::shared_ptr<SDOUpdateRequest>& request)
350  {
351  if (!isSDOAccessAvailable())
352  {
353  request->setFailed();
354  return;
355  }
356 
357  IceUtil::Time start = armarx::rtNow();
358  int wkc = -1;
359  int len = static_cast<int>(*request->buflen);
360  if (request->readRequest)
361  {
362  GENERAL_TIMING_START(sdo_read_request);
363  wkc = ec_SDOread(request->sdoIdentifier.slaveIndex,
364  request->sdoIdentifier.index,
365  request->sdoIdentifier.subIndex,
366  request->completeAccess,
367  &len,
368  request->buf,
370  *request->buflen = len; // Write back actually read buffer length.
371  GENERAL_TIMING_CEND(sdo_read_request, 1);
372  }
373  else
374  {
375  GENERAL_TIMING_START(sdo_write_request);
376  wkc = ec_SDOwrite(request->sdoIdentifier.slaveIndex,
377  request->sdoIdentifier.index,
378  request->sdoIdentifier.subIndex,
379  request->completeAccess,
380  len,
381  request->buf,
383  GENERAL_TIMING_CEND(sdo_write_request, 1);
384  }
386  if (wkc <= 0)
387  {
388  GENERAL_ERROR("%s for slave at index %u at 0x%X:%u failed:\n"
389  "Work counter: %u\n"
390  "SOEM-errorlist:\n%s",
391  request->readRequest ? "SDORead" : "SDOWrite",
392  request->sdoIdentifier.slaveIndex,
393  request->sdoIdentifier.index,
394  request->sdoIdentifier.subIndex,
395  wkc,
396  ec_elist2string());
397 
398  request->setFailed();
399  }
400  }
401 
403  {
404  requestHandlerThreadRunning.store(false);
405  requestHandlerThread.join();
406  }
407 
409  BusIO::changeStateOfSlave(uint16_t slaveIndex, EtherCATState state, bool validate)
410  {
411  EtherCATState actualState;
412  requestQueues.postChangeStateRequest(slaveIndex, state, validate, &actualState);
413  return actualState;
414  }
415 
418  {
419  EtherCATState actualState;
420  requestQueues.postChangeStateRequest(0, state, validate, &actualState);
421  return actualState;
422  }
423 
426  {
427  EtherCATState state;
428  requestQueues.postReadStatesRequest(&state);
429  return state;
430  }
431 
432  bool
433  BusIO::resetErrorRegisters(std::uint16_t slaveIndex)
434  {
435  return requestQueues.postRegisterResetRequest(slaveIndex);
436  }
437 
438  bool
439  BusIO::readRegisters(std::vector<RegisterDataList>& registerData)
440  {
441  return requestQueues.postRegisterReadRequest(registerData);
442  }
443 
444  bool
445  BusIO::readRegisters(EtherCATFrameList* frames, std::uint16_t amountFramesToRead)
446  {
447  return requestQueues.postRegisterReadRequest(frames, amountFramesToRead);
448  }
449 
450  void
451  BusIO::deactivateCOECA(std::uint16_t slaveIndex)
452  {
453  ARMARX_DEBUG << "Deactivation CoE Complete Access for slave at index " << slaveIndex;
454  if (slaveIndex <= ec_slavecount)
455  {
456  std::uint8_t config = ec_slave[slaveIndex].CoEdetails;
457  config &= ~ECT_COEDET_SDOCA;
458  ec_slave[slaveIndex].CoEdetails = config;
459  }
460  else
461  {
463  "Trying to deactivate CoE Complete Access for slave at index %u which does "
464  "not exist on the bus.",
465  slaveIndex);
466  }
467  }
468 
469  void
471  {
472  this->timeouts = timeouts;
473  }
474 
475 } // namespace armarx::control::ethercat
TIMING_START
#define TIMING_START(name)
Definition: TimeUtil.h:289
armarx::control::ethercat::BusIO::rtUpdatePDO
void rtUpdatePDO()
Updates the PDO of all slaves.
Definition: BusIO.cpp:115
armarx::aron::ret
ReaderT::InputType T & ret
Definition: rw.h:13
armarx::control::ethercat::BusIO::changeStateOfBus
EtherCATState changeStateOfBus(EtherCATState state, bool validate=true)
Definition: BusIO.cpp:417
armarx::control::ethercat::RequestQueue::postSDOWriteRequest
bool postSDOWriteRequest(SDOIdentifier sdoIdentifier, int buflen, unsigned char *buf, bool completeAccess)
Definition: RequestQueue.cpp:89
armarx::control::ethercat::RTUtility::RT_THREAD_PRIORITY
static constexpr int RT_THREAD_PRIORITY
The thread priority for rt-threads.
Definition: RTUtility.h:24
armarx::control::ethercat::BusIO::ioMap
IOMap ioMap
Definition: BusIO.h:210
armarx::control::ethercat::EtherCATState
This class is a wrapper around an enum containing the different EtherCAT states.
Definition: EtherCATState.h:25
armarx::control::ethercat::BusIO::BusIO
BusIO()
Definition: BusIO.cpp:25
index
uint8_t index
Definition: EtherCATFrame.h:59
RtTiming.h
EtherCATFrame.h
armarx::control::ethercat::Timeouts::sdoRead_us
std::uint32_t sdoRead_us
Definition: Timeouts.h:17
BusIO.h
armarx::control::ethercat::Timeouts::stateCheck_us
int stateCheck_us
Definition: Timeouts.h:16
armarx::control::ethercat::RTUtility::pinThreadToCPU
static bool pinThreadToCPU(unsigned int cpu)
Pins the calling thread to the CPU with the given id.
Definition: RTUtility.cpp:52
armarx::control::ethercat::BusIO::changeStateOfSlave
EtherCATState changeStateOfSlave(std::uint16_t slaveIndex, EtherCATState state, bool validate=true)
Definition: BusIO.cpp:409
ChangeStateRequest.h
armarx::control::ethercat::BusIO::resetErrorRegisters
bool resetErrorRegisters(std::uint16_t slaveIndex)
Definition: BusIO.cpp:433
armarx::control::ethercat::BusIO::deactivateCOECA
void deactivateCOECA(std::uint16_t slaveIndex)
This deactivates the Complete access mode in CoE for the given slave.
Definition: BusIO.cpp:451
armarx::control::ethercat::BusIO::lastWorkCounter
int lastWorkCounter
Definition: BusIO.h:206
armarx::control::ethercat::BusIO::readStates
EtherCATState readStates()
Definition: BusIO.cpp:425
armarx::control::ethercat::BusIO::setTimeouts
void setTimeouts(Timeouts const &timeouts)
Definition: BusIO.cpp:470
RegisterReadRequest.h
ReadStatesRequest.h
armarx::control::ethercat::RequestQueue::postReply
void postReply(std::shared_ptr< RequestBase > &&request)
Definition: RequestQueue.cpp:53
SDOUpdateRequest.h
GENERAL_WARNING
#define GENERAL_WARNING(...)
Definition: ErrorReporting.h:252
ARMARX_DEBUG
#define ARMARX_DEBUG
Definition: Logging.h:184
armarx::control::ethercat::RequestQueue::postRegisterResetRequest
bool postRegisterResetRequest(std::uint16_t slaveIndex)
Definition: RequestQueue.cpp:118
armarx::control::ethercat::RequestQueue::postReadStatesRequest
bool postReadStatesRequest(EtherCATState *state)
Definition: RequestQueue.cpp:111
armarx::control::ethercat::EtherCATFrameList
The EtherCATFrameList struct holds a list of EtherCAT frames that can be scheduled in round-robin-sty...
Definition: EtherCATFrame.h:124
armarx::control::ethercat
Definition: Bus.cpp:24
armarx::control::ethercat::Timeouts::registerRead_us
int registerRead_us
Definition: Timeouts.h:15
armarx::control::ethercat::RequestQueue::getNextRequest
std::shared_ptr< RequestBase > getNextRequest()
Definition: RequestQueue.cpp:42
armarx::control::ethercat::RTUtility::elevateThreadPriority
static bool elevateThreadPriority(int priority)
Elevate the thread priority of the calling thread to the given priority.
Definition: RTUtility.cpp:17
armarx::armem::Time
armarx::core::time::DateTime Time
Definition: forward_declarations.h:13
armarx::to_string
const std::string & to_string(const std::string &s)
Definition: StringHelpers.h:41
Timing.h
ErrorReporting.h
armarx::control::ethercat::BusIO::~BusIO
virtual ~BusIO()
Definition: BusIO.cpp:402
armarx::control::ethercat::BusIO::sdoAccessAvailable
bool sdoAccessAvailable
Definition: BusIO.h:201
armarx::navigation::client::validate
void validate(const std::vector< WaypointTarget > &path)
Definition: ice_conversions.h:70
armarx::control::ethercat::RequestQueue::postChangeStateRequest
bool postChangeStateRequest(std::uint16_t slaveIndex, EtherCATState state, bool validate, EtherCATState *actualState)
Definition: RequestQueue.cpp:100
TimeUtil.h
slaveConfiguredAddress
uint16_t slaveConfiguredAddress
Definition: EtherCATFrame.h:60
armarx::control::ethercat::Timeouts
Definition: Timeouts.h:8
armarx::control::ethercat::Timeouts::sdoWrite_us
std::uint32_t sdoWrite_us
Definition: Timeouts.h:18
GENERAL_TIMING_START
#define GENERAL_TIMING_START(name)
Definition: Timing.h:142
GENERAL_ERROR
#define GENERAL_ERROR(...)
Definition: ErrorReporting.h:255
Logging.h
armarx::control::ethercat::RequestQueue::postRegisterReadRequest
bool postRegisterReadRequest(std::vector< RegisterDataList > &registerData)
Definition: RequestQueue.cpp:125
TIMING_CEND_COMMENT
#define TIMING_CEND_COMMENT(name, comment, thresholdMs)
Definition: TimeUtil.h:315
RTUtility.h
armarx::control::ethercat::BusIO::timeouts
Timeouts timeouts
Definition: BusIO.h:204
armarx::control::ethercat::RequestQueue::postSDOReadRequest
bool postSDOReadRequest(SDOIdentifier sdoIdentifier, int *buflen, unsigned char *buf, bool completeAccess)
Definition: RequestQueue.cpp:78
armarx::control::ethercat::BusIO::readRegisters
bool readRegisters(std::vector< RegisterDataList > &registerData)
Definition: BusIO.cpp:439
RegisterResetRequest.h
GENERAL_TIMING_CEND
#define GENERAL_TIMING_CEND(name, thresholdMs)
Definition: Timing.h:159
armarx::rtNow
IceUtil::Time rtNow()
Definition: RtTiming.h:40