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
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
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 {
166 RegisterDataList list;
167 list.slaveIndex = i;
168 for (const auto& type : registerList)
169 {
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
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
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
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
uint16_t slaveConfiguredAddress
constexpr T c
This util class helps with keeping a cycle time during a control cycle.
Definition CycleUtil.h:41
bool readRegisters(std::vector< RegisterDataList > &registerData)
Definition BusIO.cpp:439
static Bus & getBus()
This returns the one and only Bus object.
Definition Bus.cpp:29
SlaveRegisterReadingScheduler(std::uint16_t slaveCount, unsigned int updatePeriodInMS, std::vector< datatypes::RegisterEnum > registerList)
static void updateRegisterDataFromEtherCATFrameList(const EtherCATFrameList *frameList, std::vector< RegisterDataList > *requestedRegisters)
static EtherCATFrameList * createEtherCATFrameListFromRegisterDataList(const std::vector< RegisterDataList > *requestedRegisters)
#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...
#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...
RegisterEnum
The RegisterEnum enum encodes the slave registers that every slave offers.
std::uint32_t getRegisterAddress(RegisterEnum e)
std::variant< EtherCATDataType::INTEGER8, EtherCATDataType::INTEGER16, EtherCATDataType::INTEGER32, EtherCATDataType::INTEGER64, EtherCATDataType::UNSIGNED8, EtherCATDataType::UNSIGNED16, EtherCATDataType::UNSIGNED32, EtherCATDataType::UNSIGNED64 > RegisterEnumTypeContainer
std::uint8_t getRegisterSubAddress(RegisterEnum e)
const std::unordered_map< RegisterEnum, RegisterInfo > registerMap
Holds additional information on all defined registers.
std::size_t getRegisterByteLength(datatypes::RegisterEnum reg)
Get the length of a register in bytes, rounded up.
Interval< T > interval(T lo, T hi)
constexpr std::size_t find(string_view str, char_type c) noexcept
The EtherCATFrameList struct holds a list of EtherCAT frames that can be scheduled in round-robin-sty...
std::vector< std::pair< EtherCATFrame, EtherCATFrameMetaData > > list
The PDUMetaData struct holds information about the structure of a PDU.
Brief description of struct RegisterDataList.
std::map< datatypes::RegisterEnum, datatypes::RegisterEnumTypeContainer > registerData
#define ARMARX_TRACE
Definition trace.h:77