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 {
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,
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.",
108 timeouts.registerRead_us);
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(),
234 timeouts.stateCheck_us,
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,
369 timeouts.sdoRead_us);
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,
382 timeouts.sdoWrite_us);
383 GENERAL_TIMING_CEND(sdo_write_request, 1);
384 }
385 IceUtil::Time end = armarx::rtNow();
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
#define GENERAL_ERROR(...)
#define GENERAL_WARNING(...)
uint16_t slaveConfiguredAddress
uint8_t index
#define GENERAL_TIMING_START(name)
Definition Timing.h:142
#define GENERAL_TIMING_CEND(name, thresholdMs)
Definition Timing.h:159
void deactivateCOECA(std::uint16_t slaveIndex)
This deactivates the Complete access mode in CoE for the given slave.
Definition BusIO.cpp:451
void setTimeouts(Timeouts const &timeouts)
Definition BusIO.cpp:470
EtherCATState changeStateOfSlave(std::uint16_t slaveIndex, EtherCATState state, bool validate=true)
Definition BusIO.cpp:409
void rtUpdatePDO()
Updates the PDO of all slaves.
Definition BusIO.cpp:115
bool readRegisters(std::vector< RegisterDataList > &registerData)
Definition BusIO.cpp:439
EtherCATState changeStateOfBus(EtherCATState state, bool validate=true)
Definition BusIO.cpp:417
bool resetErrorRegisters(std::uint16_t slaveIndex)
Definition BusIO.cpp:433
This class is a wrapper around an enum containing the different EtherCAT states.
static bool pinThreadToCPU(unsigned int cpu)
Pins the calling thread to the CPU with the given id.
Definition RTUtility.cpp:52
static bool elevateThreadPriority(int priority)
Elevate the thread priority of the calling thread to the given priority.
Definition RTUtility.cpp:17
static constexpr int RT_THREAD_PRIORITY
The thread priority for rt-threads.
Definition RTUtility.h:24
bool postSDOReadRequest(SDOIdentifier sdoIdentifier, int *buflen, unsigned char *buf, bool completeAccess)
std::shared_ptr< RequestBase > getNextRequest()
bool postSDOWriteRequest(SDOIdentifier sdoIdentifier, int buflen, unsigned char *buf, bool completeAccess)
#define ARMARX_DEBUG
The logging level for output that is only interesting while debugging.
Definition Logging.h:184
#define TIMING_START(name)
Helper macro to do timing tests.
Definition TimeUtil.h:289
#define TIMING_CEND_COMMENT(name, comment, thresholdMs)
Prints duration with comment in front of it if it took longer than threshold.
Definition TimeUtil.h:315
IceUtil::Time rtNow()
Definition RtTiming.h:40
The EtherCATFrameList struct holds a list of EtherCAT frames that can be scheduled in round-robin-sty...
The EtherCATFrame struct represents an EtherCAT frame according to the EtherCAT spec.