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