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