SlaveRegisterReadingScheduler.cpp
Go to the documentation of this file.
2 
3 #include <algorithm>
4 #include <cmath>
5 #include <cstring> // for memcpy
6 
13 
14 #include "ethercat.h"
15 
17 {
18 
19  EtherCATFrameList*
21  const std::vector<RegisterDataList>* requestedRegisters)
22  {
23  ARMARX_CHECK_NOT_NULL(requestedRegisters);
24 
25  std::vector<std::pair<std::uint16_t, std::vector<std::pair<int, int>>>> intervals;
26 
27  for (const auto& registerList : *requestedRegisters)
28  {
29  ARMARX_CHECK_LESS_EQUAL(registerList.slaveIndex, ec_slavecount);
30  auto frameIntervals = createFrameIntervals(registerList);
31  intervals.push_back({registerList.slaveIndex, frameIntervals});
32  }
33 
34  return createEtherCATFrameList(intervals);
35  }
36 
37  void
39  const EtherCATFrameList* frameList,
40  std::vector<RegisterDataList>* requestedRegisters)
41  {
42  ARMARX_CHECK_NOT_NULL(requestedRegisters);
43  ARMARX_CHECK_NOT_NULL(frameList);
44 
45  auto getRegisterDataForSlave =
46  [&requestedRegisters](std::uint16_t slaveConfiguredIndex) -> RegisterDataList&
47  {
48  std::uint16_t slaveIndex = 0;
49  for (int i = 1; i <= ec_slavecount; i++)
50  {
51  if (ec_slave[i].configadr == slaveConfiguredIndex)
52  {
53  slaveIndex = static_cast<std::uint16_t>(i);
54  break;
55  }
56  }
57 
58  auto it = std::find_if(requestedRegisters->begin(),
59  requestedRegisters->end(),
60  [&slaveIndex](const RegisterDataList& list)
61  { return list.slaveIndex == slaveIndex; });
62  return *it;
63  };
64 
65  using RegisterDataIterator =
66  std::map<datatypes::RegisterEnum, datatypes::RegisterEnumTypeContainer>::iterator;
67 
68  auto copyDataFromPduEntryToRegisterDataContainer =
69  [&](const std::uint8_t* dataPtr, RegisterDataIterator registerDataEntry)
70  {
71  std::uint64_t result = 0;
72 
73  std::size_t bitlength = datatypes::registerMap.at(registerDataEntry->first).bitLength;
74  std::uint8_t subAddress = datatypes::getRegisterSubAddress(registerDataEntry->first);
75  switch (getRegisterByteLength(registerDataEntry->first))
76  {
77  case 1:
78  result = (*dataPtr) >> subAddress & ((0x1 << bitlength) - 1);
79  registerDataEntry->second =
80  static_cast<datatypes::EtherCATDataType::UNSIGNED8>(result);
81  break;
82  case 2:
83  std::memcpy(&result, dataPtr, sizeof(datatypes::EtherCATDataType::UNSIGNED16));
84  registerDataEntry->second =
85  static_cast<datatypes::EtherCATDataType::UNSIGNED16>(result);
86  break;
87  case 4:
88  std::memcpy(&result, dataPtr, sizeof(datatypes::EtherCATDataType::UNSIGNED32));
89  registerDataEntry->second =
90  static_cast<datatypes::EtherCATDataType::UNSIGNED32>(result);
91  break;
92  case 8:
93  std::memcpy(&result, dataPtr, sizeof(datatypes::EtherCATDataType::UNSIGNED64));
94  registerDataEntry->second =
95  static_cast<datatypes::EtherCATDataType::UNSIGNED64>(result);
96  break;
97  }
98  };
99 
100 
101  ARMARX_TRACE;
102 
103  for (const auto& [frame, metaData] : frameList->list)
104  {
105  const uint8_t* frameData = reinterpret_cast<const uint8_t*>(&frame); // NOLINT
106  for (const auto& pdu : metaData.pdus)
107  {
108  RegisterDataList& data = getRegisterDataForSlave(pdu.slaveConfiguredAddress);
109 
110  std::uint16_t wkc = 0;
111  std::memcpy(&wkc, frameData + pdu.workingCounterOffset, sizeof(uint16_t)); // NOLINT
112  if (wkc == 0)
113  {
114  continue;
115  }
116 
117  RegisterDataIterator registerDataIt = data.registerData.begin();
118 
119  bool skipped = false;
120  for (const auto& [type, offset] : pdu.registerOffsets)
121  {
122  // Skip entries in registerData, that are not part of this pdu
123  if (!skipped)
124  {
125  while (registerDataIt->first < type)
126  registerDataIt++;
127  // If the current register data entry points NOT to a subaddress of the
128  // current pdu entry, we have to go one step back
129  if (datatypes::getRegisterAddress(registerDataIt->first) >
131  registerDataIt--;
132  skipped = true;
133  }
134 
135  if (registerDataIt->first == type)
136  {
137  // We found the registerData entry to fill with the data of this pdu entry
138  copyDataFromPduEntryToRegisterDataContainer(frameData + offset,
139  registerDataIt);
140  registerDataIt++;
141  }
142 
143  while (datatypes::getRegisterAddress(registerDataIt->first) ==
145  {
146  // The registerData entry is a subaddress of the current pdu entry
147  copyDataFromPduEntryToRegisterDataContainer(frameData + offset,
148  registerDataIt);
149  registerDataIt++;
150  }
151  }
152  }
153  }
154  }
155 
156 
158  uint16_t slaveCount,
159  unsigned int updatePeriodInMS,
160  std::vector<datatypes::RegisterEnum> registerList) :
161  updatePeriodInMS(updatePeriodInMS)
162  {
163  for (std::uint16_t i = 1; i < slaveCount + 1; i++)
164  {
166  list.slaveIndex = i;
167  for (const auto& type : registerList)
168  {
169  list.registerData.insert({type, datatypes::RegisterEnumTypeContainer()});
170  }
171  registerData.push_back(list);
172  }
173 
174  frameList = createEtherCATFrameListFromRegisterDataList(&registerData);
175 
176  errorRegisterUpdateThread = std::thread(&SlaveRegisterReadingScheduler::updateLoop, this);
177  }
178 
180  {
181  errorRegisterUpdateThreadRunning.store(false);
182  errorRegisterUpdateThread.join();
183  }
184 
185  const std::vector<RegisterDataList>&
187  {
188  return registerData;
189  }
190 
191  void
193  {
194  if (readyToReadNextRegisters.load(std::memory_order_relaxed))
195  {
196  cv_readNextRegisters.notify_all();
197  }
198  }
199 
200  bool
202  {
203  return areAllRegistersUpdated.load(std::memory_order_relaxed);
204  }
205 
206  void
207  SlaveRegisterReadingScheduler::updateLoop()
208  {
209  CycleUtil c(updatePeriodInMS);
210  while (errorRegisterUpdateThreadRunning.load() == true)
211  {
212  std::unique_lock<std::mutex> lock{mutex_readNextRegisters};
213  cv_readNextRegisters.wait(lock);
214  readyToReadNextRegisters.store(false, std::memory_order_relaxed);
215 
216  Bus::getBus().readRegisters(frameList, 1);
217  frameList->nextIndex = (frameList->nextIndex + 1) % frameList->list.size();
218 
219  if (frameList->nextIndex == 0)
220  {
222  frameList, &registerData);
223  areAllRegistersUpdated.store(true, std::memory_order_relaxed);
224  }
225  else
226  {
227  areAllRegistersUpdated.store(false, std::memory_order_relaxed);
228  }
229 
230 
231  if (allRegistersUpdated())
232  {
233  c.waitForCycleDuration();
234  }
235 
236  readyToReadNextRegisters.store(true, std::memory_order_relaxed);
237  }
238  }
239 
240  std::vector<int>
241  SlaveRegisterReadingScheduler::registerReadMapToAddressList(const RegisterDataList& toRead)
242  {
243  std::vector<int> addressesToRead;
244  for (const auto& [registerType, _] : toRead.registerData)
245  {
246  // The higher bytes are used to index into a byte in our register numbering scheme
247  // - We don't need that here.
248  static constexpr int addressMask = 0xFFFF;
249  static constexpr float byteLength = 8.0;
250 
251  int baseAddress = static_cast<int>(registerType) & addressMask;
252 
253  for (int i = 0; // NOLINTNEXTLINE(bugprone-narrowing-conversions)
254  i < std::ceil(datatypes::registerMap.at(registerType).bitLength / byteLength);
255  ++i)
256  {
257  addressesToRead.push_back(baseAddress + i);
258  }
259  }
260  std::sort(addressesToRead.begin(), addressesToRead.end());
261 
262  addressesToRead.erase(std::unique(addressesToRead.begin(), addressesToRead.end()),
263  addressesToRead.end());
264 
265  return addressesToRead;
266  }
267 
268  std::vector<std::pair<int, int>>
269  SlaveRegisterReadingScheduler::createFrameIntervals(const RegisterDataList& toRead)
270  {
271  if (toRead.registerData.empty())
272  {
273  return {};
274  }
275 
276  std::vector<int> addressesToRead(registerReadMapToAddressList(toRead));
277 
278  std::vector<std::pair<int, int>> pduIntervals;
279  int currentIntervalStart = -1;
280  int currentIntervalEnd = 0;
281 
282  for (int address : addressesToRead)
283  {
284  // If we've started accumulating an interval && adding the next address
285  // adds fewer bytes than just starting a new PDU && our PDU hasn't grown too large
286  if (currentIntervalStart >= 0 && address - currentIntervalEnd - 1 < pduOverhead &&
287  address + 1 - currentIntervalStart + pduOverhead <
288  static_cast<int>(maxTotalPDULength))
289  {
290  currentIntervalEnd = address + 1;
291  }
292  else
293  {
294  if (currentIntervalStart >= 0)
295  {
296  pduIntervals.push_back({currentIntervalStart, currentIntervalEnd});
297  }
298  currentIntervalStart = address;
299  currentIntervalEnd = address + 1;
300  }
301  }
302  pduIntervals.push_back({currentIntervalStart, currentIntervalEnd});
303  return pduIntervals;
304  }
305 
306  PDUMetaData
307  SlaveRegisterReadingScheduler::createPDUMetaData(std::tuple<uint16_t, int, int> pduInterval,
308  size_t pduOffset)
309  {
310  PDUMetaData result;
311  result.slaveConfiguredAddress = std::get<0>(pduInterval);
312  size_t dataLength = std::get<2>(pduInterval) - std::get<1>(pduInterval);
313  result.workingCounterOffset = pduOffset + sizeof(EtherCATPDU) - 1 + dataLength;
314 
315  // This is kinda ugly and also somewhat inefficient. I just haven't found a nice way
316  // to get the required information from the top-level call all the way down here.
317  for (int address = std::get<1>(pduInterval); address < std::get<2>(pduInterval); ++address)
318  {
319  datatypes::RegisterEnum addressAsEnum = static_cast<datatypes::RegisterEnum>(address);
320  if (datatypes::registerMap.find(addressAsEnum) != datatypes::registerMap.end())
321  {
322  result.registerOffsets[addressAsEnum] =
323  address - std::get<1>(pduInterval) + sizeof(EtherCATPDU) - 1 + pduOffset;
324  }
325  }
326  return result;
327  }
328 
329  std::pair<EtherCATFrame, EtherCATFrameMetaData>
330  SlaveRegisterReadingScheduler::createEtherCATFrame(
331  std::vector<std::tuple<uint16_t, int, int>> slaveAssignedPDUIntervals)
332  {
333  EtherCATFrame frame{};
334  EtherCATFrameMetaData metaData{};
335 
336  // Remember where we will write the next PDU
337  uint8_t* currentLocation = static_cast<uint8_t*>(frame.pduArea);
338  for (auto it = slaveAssignedPDUIntervals.begin(); it != slaveAssignedPDUIntervals.end();
339  ++it)
340  {
341  uint16_t dataLength = std::get<2>(*it) - std::get<1>(*it);
342  if (currentLocation + dataLength + pduOverhead // NOLINT
343  >= static_cast<uint8_t*>(frame.pduArea + maxTotalPDULength)) // NOLINT
344  {
345  throw std::length_error(
346  "Total PDU length exceeded maximum EtherCAT frame capacity.");
347  }
348 
349  EtherCATPDU* pdu = new (currentLocation) EtherCATPDU; // NOLINT
350 
351  pdu->slaveConfiguredAddress = std::get<0>(*it);
352  pdu->registerAddress = std::get<1>(*it);
353 
354  if (it + 1 != slaveAssignedPDUIntervals.end())
355  {
356  static constexpr uint16_t hasNextPDU = 1 << 15;
357  pdu->dataLengthAndNext = dataLength | hasNextPDU;
358  }
359  else
360  {
361  pdu->dataLengthAndNext = dataLength;
362  }
363 
364  metaData.pdus.push_back(createPDUMetaData(
365  *it, currentLocation - reinterpret_cast<uint8_t*>(&frame))); // NOLINT
366 
367  currentLocation += sizeof(EtherCATPDU) - 1 + dataLength + sizeof(uint16_t); // NOLINT
368  }
369  uint16_t frameLength =
370  currentLocation - static_cast<uint8_t*>(frame.pduArea) + sizeof(uint16_t);
371  metaData.lengthOfFrame = frameLength;
372 
373  static constexpr uint16_t frameTypePDUs = 0x1000;
374  frame.lengthAndType = (frameLength - 2) | frameTypePDUs;
375 
376  return {frame, metaData};
377  }
378 
379  EtherCATFrameList*
380  SlaveRegisterReadingScheduler::createEtherCATFrameList(
381  const std::vector<std::pair<std::uint16_t, std::vector<std::pair<int, int>>>>& pduIntervals)
382  {
383  // We need the slave addresses later when generating the frames,
384  // so we add them in here.
385  std::vector<std::tuple<std::uint16_t, int, int>> slaveAssignedPDUIntervals;
386  for (const auto& [slaveIndex, intervals] : pduIntervals)
387  {
388  std::uint16_t slaveConfiguredAddress = ec_slave[slaveIndex].configadr;
389  for (const std::pair<int, int>& interval : intervals)
390  {
391  slaveAssignedPDUIntervals.push_back(
392  {slaveConfiguredAddress, interval.first, interval.second});
393  }
394  }
395 
396  // TODO: This most likely leaks memory
397  EtherCATFrameList* frameList = new EtherCATFrameList(); // NOLINT
398  int frameTotalSize = 0;
399  std::vector<std::tuple<uint16_t, int, int>> nextFrameIntervals;
400 
401  // We add in PDUs until we can't fit the next, then start a new frame.
402  for (const auto& interval : slaveAssignedPDUIntervals)
403  {
404  int nextIntervalLength = std::get<2>(interval) - std::get<1>(interval);
405  if (frameTotalSize + nextIntervalLength + pduOverhead <
406  static_cast<int>(maxTotalPDULength))
407  {
408  nextFrameIntervals.push_back(interval);
409  frameTotalSize += nextIntervalLength + pduOverhead;
410  }
411  else
412  {
413  frameList->list.push_back(createEtherCATFrame(nextFrameIntervals));
414  nextFrameIntervals.clear();
415  frameTotalSize = 0;
416  }
417  }
418  frameList->list.push_back(createEtherCATFrame(nextFrameIntervals));
419  return frameList;
420  }
421 
422 } // namespace armarx::control::ethercat
armarx::control::ethercat::getRegisterByteLength
std::size_t getRegisterByteLength(datatypes::RegisterEnum reg)
Get the length of a register in bytes, rounded up.
Definition: SlaveRegisters.cpp:6
armarx::control::ethercat::datatypes::EtherCATDataType::UNSIGNED16
uint16_t UNSIGNED16
Definition: EtherCATDataTypes.h:72
RtTiming.h
EtherCATFrame.h
armarx::control::ethercat::datatypes::EtherCATDataType::UNSIGNED8
uint8_t UNSIGNED8
Definition: EtherCATDataTypes.h:71
list
list(APPEND SOURCES ${QT_RESOURCES}) set(COMPONENT_LIBS ArmarXGui ArmarXCoreObservers ArmarXCoreEigen3Variants PlotterController $
Definition: CMakeLists.txt:49
armarx::control::ethercat::datatypes::registerMap
const std::unordered_map< RegisterEnum, RegisterInfo > registerMap
Holds additional information on all defined registers.
Definition: SlaveRegisters.h:182
ARMARX_CHECK_NOT_NULL
#define ARMARX_CHECK_NOT_NULL(ptr)
This macro evaluates whether ptr is not null and if it turns out to be false it will throw an Express...
Definition: ExpressionException.h:206
armarx::control::ethercat::datatypes::getRegisterSubAddress
std::uint8_t getRegisterSubAddress(RegisterEnum e)
Definition: SlaveRegisters.h:116
armarx::CycleUtil
This util class helps with keeping a cycle time during a control cycle.
Definition: CycleUtil.h:40
armarx::control::ethercat::datatypes::RegisterEnum
RegisterEnum
The RegisterEnum enum encodes the slave registers that every slave offers.
Definition: SlaveRegisters.h:37
c
constexpr T c
Definition: UnscentedKalmanFilterTest.cpp:43
armarx::control::ethercat::SlaveRegisterReadingScheduler::updateRegisterDataFromEtherCATFrameList
static void updateRegisterDataFromEtherCATFrameList(const EtherCATFrameList *frameList, std::vector< RegisterDataList > *requestedRegisters)
Definition: SlaveRegisterReadingScheduler.cpp:38
armarx::control::ethercat::datatypes::EtherCATDataType::UNSIGNED64
uint64_t UNSIGNED64
Definition: EtherCATDataTypes.h:84
armarx::control::ethercat::SlaveRegisterReadingScheduler::~SlaveRegisterReadingScheduler
~SlaveRegisterReadingScheduler()
Definition: SlaveRegisterReadingScheduler.cpp:179
armarx::control::ethercat::EtherCATFrameList::list
std::vector< std::pair< EtherCATFrame, EtherCATFrameMetaData > > list
Definition: EtherCATFrame.h:127
Bus.h
ARMARX_TRACE
#define ARMARX_TRACE
Definition: trace.h:69
armarx::control::ethercat::SlaveRegisterReadingScheduler::getRegisterData
const std::vector< RegisterDataList > & getRegisterData()
Definition: SlaveRegisterReadingScheduler.cpp:186
armarx::control::ethercat::datatypes::getRegisterAddress
std::uint32_t getRegisterAddress(RegisterEnum e)
Definition: SlaveRegisters.h:109
armarx::control::ethercat::datatypes::RegisterEnumTypeContainer
std::variant< EtherCATDataType::INTEGER8, EtherCATDataType::INTEGER16, EtherCATDataType::INTEGER32, EtherCATDataType::INTEGER64, EtherCATDataType::UNSIGNED8, EtherCATDataType::UNSIGNED16, EtherCATDataType::UNSIGNED32, EtherCATDataType::UNSIGNED64 > RegisterEnumTypeContainer
Definition: SlaveRegisters.h:160
armarx::interval
Interval< T > interval(T lo, T hi)
Definition: OccupancyGrid.h:26
armarx::control::ethercat::RegisterDataList
Brief description of struct RegisterDataList.
Definition: SlaveRegisters.h:316
armarx::control::ethercat::EtherCATFrameList::nextIndex
size_t nextIndex
Definition: EtherCATFrame.h:126
data
uint8_t data[1]
Definition: EtherCATFrame.h:68
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::Bus::getBus
static Bus & getBus()
This returns the one and only Bus object.
Definition: Bus.cpp:29
armarx::control::ethercat::SlaveRegisterReadingScheduler::createEtherCATFrameListFromRegisterDataList
static EtherCATFrameList * createEtherCATFrameListFromRegisterDataList(const std::vector< RegisterDataList > *requestedRegisters)
Definition: SlaveRegisterReadingScheduler.cpp:20
ARMARX_CHECK_LESS_EQUAL
#define ARMARX_CHECK_LESS_EQUAL(lhs, rhs)
This macro evaluates whether lhs is less or equal (<=) rhs and if it turns out to be false it will th...
Definition: ExpressionException.h:109
armarx::control::ethercat::SlaveRegisterReadingScheduler::allRegistersUpdated
bool allRegistersUpdated() const
Definition: SlaveRegisterReadingScheduler.cpp:201
CycleUtil.h
ExpressionException.h
armarx::control::ethercat::datatypes::EtherCATDataType::UNSIGNED32
uint32_t UNSIGNED32
Definition: EtherCATDataTypes.h:73
SlaveRegisterReadingScheduler.h
armarx::control::ethercat::SlaveRegisterReadingScheduler::SlaveRegisterReadingScheduler
SlaveRegisterReadingScheduler(std::uint16_t slaveCount, unsigned int updatePeriodInMS, std::vector< datatypes::RegisterEnum > registerList)
Definition: SlaveRegisterReadingScheduler.cpp:157
armarx::control::ethercat::datatypes::EtherCATDataType::bitLength
constexpr int bitLength
Defines the length of the various EtherCATDataTypes in bits.
Definition: EtherCATDataTypes.h:104
slaveConfiguredAddress
uint16_t slaveConfiguredAddress
Definition: EtherCATFrame.h:60
Logging.h
armarx::control::ethercat::SlaveRegisterReadingScheduler::startReadingNextRegisters
void startReadingNextRegisters()
Definition: SlaveRegisterReadingScheduler.cpp:192
armarx::control::ethercat::BusIO::readRegisters
bool readRegisters(std::vector< RegisterDataList > &registerData)
Definition: BusIO.cpp:433