1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578 | /* File: coe_utilities.h
* This file is part of the program ethercatcpp-core
* Program description : EtherCAT driver libraries for UNIX
* Copyright (C) 2017-2024 - Robin Passama (LIRMM / CNRS) Arnaud Meline
* (LIRMM / CNRS) Benjamin Navarro (LIRMM / CNRS). All Right reserved.
*
* This software is free software: you can redistribute it and/or modify
* it under the terms of the CeCILL-C license as published by
* the CEA CNRS INRIA, either version 1
* of the License, or (at your option) any later version.
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* CeCILL-C License for more details.
*
* You should have received a copy of the CeCILL-C License
* along with this software. If not, it can be found on the official
* website of the CeCILL licenses family (http://www.cecill.info/index.en.html).
*/
/**
* @file coe_utilities.h
* @author Robin Passama
* @brief Main header file for CoE utilities
* @date November 2024
* @ingroup ethercatcpp-core
*/
#pragma once
#include <cstdint>
#include <map>
#include <string_view>
#include <vector>
#include <cmath>
namespace ethercatcpp {
class SlaveDevice;
namespace coe {
constexpr const uint16_t coe_rx_pdo_map_1 = 0x1600;
constexpr const uint16_t coe_rx_pdo_map_2 = 0x1601;
constexpr const uint16_t coe_rx_pdo_map_3 = 0x1602;
constexpr const uint16_t coe_rx_pdo_map_4 = 0x1603;
constexpr const uint16_t coe_tx_pdo_map_1 = 0x1A00;
constexpr const uint16_t coe_tx_pdo_map_2 = 0x1A01;
constexpr const uint16_t coe_tx_pdo_map_3 = 0x1A02;
constexpr const uint16_t coe_tx_pdo_map_4 = 0x1A03;
constexpr const uint16_t coe_rx_pdo_param_1 = 0x1400;
constexpr const uint16_t coe_rx_pdo_param_2 = 0x1401;
constexpr const uint16_t coe_rx_pdo_param_3 = 0x1402;
constexpr const uint16_t coe_rx_pdo_param_4 = 0x1403;
constexpr const uint16_t coe_tx_pdo_param_1 = 0x1800;
constexpr const uint16_t coe_tx_pdo_param_2 = 0x1801;
constexpr const uint16_t coe_tx_pdo_param_3 = 0x1802;
constexpr const uint16_t coe_tx_pdo_param_4 = 0x1803;
static constexpr uint16_t coe_rx_pdo_assign = 0x1c12;
static constexpr uint16_t coe_tx_pdo_assign = 0x1c13;
/**
* @brief represent the CoE dictionnary of a device
*
*/
class ObjectDictionary {
public:
/**
* @brief Entry of a dictionnary
*
*/
struct DictionaryEntry {
uint16_t addr;<--- struct member 'DictionaryEntry::addr' is never used.
uint8_t subindex;<--- struct member 'DictionaryEntry::subindex' is never used.
uint8_t bits;<--- struct member 'DictionaryEntry::bits' is never used.
};
using entry = DictionaryEntry;
ObjectDictionary() = default;
~ObjectDictionary() = default;
/**
* @brief Construct a new Object Dictionary object from a list of entries
* @param initializer list that describe dictionary content
*/
explicit ObjectDictionary(
std::initializer_list<std::pair<std::string_view, entry>>);
/**
* @brief Get the compact specification of a given entry
*
* @param name the object name
* @return uint32_t the compact CoE representation of the entry
*/
uint32_t mapped_pdo_object(std::string_view name) const;
/**
* @brief Get address of a given entry
*
* @param name the object name
* @return uint16_t the address of the entry in dictionnary memory
*/
uint16_t addr(std::string_view name) const;
/**
* @brief Get size of a given entry
*
* @param name the object name
* @return the size of the object in bytes
*/
size_t size(std::string_view name) const;
/**
* @brief Get uncompressed specification of a given entry
*
* @param name the object name
* @return std::tuple<uint16_t, uint8_t, uint8_t> the specification of the
* entry (address, sub index, size in bits)
*/
std::tuple<uint16_t, uint8_t, uint8_t> object(std::string_view name) const;
/**
* @brief Get uncompressed specification from a compact object specification
*
* @param full_spec the compact object specification
* @return std::tuple<uint16_t, uint8_t, uint8_t> the specification of the
* entry (address, sub index, size in bits)
*/
static std::tuple<uint16_t, uint8_t, uint8_t>
pdo_object_specs(uint32_t full_spec);
/**
* @brief Add en entry to the dictionary
*
* @param name the objectname
* @param object the corresponding entry specification
*/
void add_entry(std::string_view name, const DictionaryEntry& object);
/**
* @brief Add en entry to the dictionary
*
* @param entries a list of entries to be added
*/
void add_entries(
std::initializer_list<std::pair<std::string_view, entry>> entries);
private:
const ObjectDictionary::DictionaryEntry&
get_entry_safe(std::string_view name, std::string_view caller) const;
std::map<uint64_t, DictionaryEntry> dictionary_;
};
using dico_t = ObjectDictionary;
/**
* @brief Represent a CANOpen PDO mapping
*
*/
class PDOMapping {
ObjectDictionary* reference_dictionnary_;
uint8_t index_;
bool is_tx_;
std::vector<std::pair<uint8_t, uint32_t>> objects_mapping_;
void check_entry_throws(std::string_view obj) const;
public:
using iterator = std::vector<std::pair<uint8_t, uint32_t>>::iterator;
using const_iterator =
std::vector<std::pair<uint8_t, uint32_t>>::const_iterator;
/**
* @brief Construct a new PDOMapping object
* @details index of the mapping is relative to 0x1600 in RX and 0x1A00 if
* TX
* @param dico the dictionary referenced by the PDO mapping
* @param idx index of the mapping
* @param is_tx true if tx (master in) mapping, false if rx (master out)
* mapping
*/
PDOMapping(ObjectDictionary& dico, uint8_t idx, bool is_tx);
/**
* @brief Construct a new PDOMapping object
* @details index of the mapping is relative to 0x1600 in RX and 0x1A00 if
* TX
* @param dico the dictionary referenced by the PDO mapping
* @param idx index of the mapping
* @param is_tx true if tx (master in) mapping, false if rx (master out)
* mapping
* @param objects list of objects (in order) that define the mapping
*/
PDOMapping(ObjectDictionary& dico, uint8_t idx, bool is_tx,
std::initializer_list<std::string_view> objects);
/**
* @brief get address of the mapping in device memory
* @details depends on specified TR/RX and index
* @return uint16_t the mapping address
*/
uint16_t map_addr() const;
/**
* @brief Check whether the mapping use the corresponding entry
* @param entry the CANOpen dictionary entry to check
* @return true if mapping has entry, false otherwise
*/
bool has_entry(std::string_view entry) const;
/**
* @brief get first iterator on mapped objects
*
* @return const_iterator
*/
const_iterator begin() const;
/**
* @brief get terminal iterator on mapped objects
*
* @return const_iterator
*/
const_iterator end() const;
/**
* @brief clean mapping memory
*
*/
void reset();
/**
* @brief add an object to the mapping
* @param obj the name of the object's entry in dictionary
*
*/
void add_object(std::string_view obj);
/**
* @brief Configure an ethercat unit device with the corresponding mapping
* @details will reset the mapping in device memory and will reset it
* accordingly to the mapping description
* @param eth_slave the unit device to configure
* @return true i configuration succeeded, false otherwise
*/
bool configure(SlaveDevice& eth_slave) const;
/**
* @brief Get total size in bytes of the mapping
*
* @return size_t the size of the mapping
*/
size_t memory_size() const;
/**
* @brief Get memory shift to access a given object of the mapping
* @param obj the name of the object in dictionary
* @return size_t the shift to access the object, relative to mapping memory
*/
size_t memory_shift(std::string_view obj) const;
/**
* @brief Check whether a type can be used as a buffer for this mapping
*
* @tparam T the type to check against
* @return true if type size match, false otherwise
*/
template <typename T>
bool check_buffer() {<--- Technically the member function 'ethercatcpp::coe::PDOMapping::check_buffer' can be const. [+]The member function 'ethercatcpp::coe::PDOMapping::check_buffer' can be made a const function. Making this function 'const' should not cause compiler errors. Even though the function can be made const function technically it may not make sense conceptually. Think about your design and the task of the function first - is it a function that must not change object internal state?
return memory_size() == sizeof(T);
}
/**
* @brief Check whether an entry has corresponding size
*
* @param obj the object to check
* @param bytes the size to check
* @return true if type size match, false otherwise
*/
bool check_entry_size(std::string_view obj, size_t bytes) const;
/**
* @brief Give the type of mapping (TX or RX)
*
* @return true if mapping is a TX mapping, false if it is a RX mapping
*/
bool is_tx() const;
/**
* @brief Access reference dictionary
*
* @return const reference on dictionary used by mapping
*/
const ObjectDictionary& dictionary() const;
};
class PDOBuffer {
std::vector<const PDOMapping*> mappings_;
uint8_t* data_;
bool is_tx_;
uint16_t addr_;
uint32_t flags_;
void check_mapping_throws(const PDOMapping& mapping) const;
void check_mapping_size_throws(const PDOMapping& mapping,
size_t bytes) const;
uint8_t* map_memory_internal(const PDOMapping& mapping, size_t bytes);
const uint8_t* map_memory_internal(const PDOMapping& mapping,
size_t bytes) const;
const uint8_t* map_memory_internal(const PDOMapping& mapping,
std::string_view entry_name,
size_t bytes) const;
uint8_t* map_memory_internal(const PDOMapping& mapping,
std::string_view entry_name, size_t bytes);
void check_entry_throws(const PDOMapping& mapping,<--- Technically the member function 'ethercatcpp::coe::PDOBuffer::check_entry_throws' can be static (but you may consider moving to unnamed namespace). [+]The member function 'ethercatcpp::coe::PDOBuffer::check_entry_throws' can be made a static function. Making a function static can bring a performance benefit since no 'this' instance is passed to the function. This change should not cause compiler errors but it does not necessarily make sense conceptually. Think about your design and the task of the function first - is it a function that must not access members of class instances? And maybe it is more appropriate to move this function to a unnamed namespace.
std::string_view entry_name, size_t bytes) const;
size_t compute_shift(const PDOMapping& mapping,
std::string_view entry_name) const;
const PDOMapping& access_mapping(size_t idx) const;
template <typename T>
std::tuple<T&> map_memory_tuple(size_t mapping_increment) {
return std::tuple<T&>(map_memory<T>(access_mapping(mapping_increment)));
}
template <typename T, typename U, typename... Other>
std::tuple<T&, U&, Other&...> map_memory_tuple(size_t mapping_increment) {
return std::tuple_cat(
map_memory_tuple<T>(mapping_increment),
map_memory_tuple<U, Other...>(mapping_increment + 1));
}
template <typename T, typename U, typename... Other>
std::tuple<const T&, const U&, const Other&...>
map_memory_tuple(size_t mapping_increment) const {
return std::tuple_cat(
map_memory_tuple<T>(mapping_increment),
map_memory_tuple<U, Other...>(mapping_increment + 1));
}
template <typename T>
std::tuple<const T&> map_memory_tuple(size_t mapping_increment) const {
return std::tuple<const T&>(
map_memory<T>(access_mapping(mapping_increment)));
}
public:
PDOBuffer() = delete;
PDOBuffer(bool is_tx, uint16_t addr_, uint32_t flags_);
template <typename... T>
PDOBuffer(bool is_tx, uint16_t addr, uint32_t flags, T&&... mappings)<--- Member variable 'PDOBuffer::data_' is not initialized in the constructor.
: PDOBuffer(is_tx, addr, flags) {
add_mappings(std::forward<T>(mappings)...);
}
/**
* @brief add a mapping to the buffer
* @param mapping the mapping to add
* @return true if mapping added false otherwise
*/
bool add_mapping(const PDOMapping& mapping, bool may_throw = false);
/**
* @brief Fill buffer from a list of mappings
*
* @tparam T is a variadic template made of any number of PDOMapping
* @param mappings the list of PDO mappings
*/
template <typename... T>
void add_mappings(T&&... mappings) {
(add_mapping(std::forward<T>(mappings), true), ...);
}
/**
* @brief Bind the buffer to a memory zone
*
* @param data the pointer to the memory zone to bind
*/
void bind(uint8_t* data);
/**
* @brief Tell whether the buffer contains a given mapping
* @param mapping the mapping to check
* @return true if it contains the mapping, false otherwise
*/
bool contains_mapping(const PDOMapping& mapping) const;
/**
* @brief Get the shift in buffer memory to access mapping data
*
* @param mapping the mapping to get shifting for
* @return size_t the number of bytes to shift
*/
size_t mapping_memory_shift(const PDOMapping& mapping) const;
/**
* @brief Get the shift in buffer memory to access a mapping entry data
*
* @param mapping the mapping defining the entry
* @param entry the name of the entry
* @return size_t the number of bytes to shift
*/
size_t entry_memory_shift(const PDOMapping& mapping,
std::string_view entry) const;
/**
* @brief Map the entry of a mapping to a typed reference
* @tparam T type of the mapped reference
* @return reference to the memory zone containing the variable
* @see bind_physical_buffer()
*/
template <typename T>
T& map_memory(const PDOMapping& mapping, std::string_view entry_name) {<--- Function parameter 'entry_name' should be passed by const reference. [+]Parameter 'entry_name' is passed by value. It could be passed as a const reference which is usually faster and recommended in C++.
return *reinterpret_cast<T*>(
map_memory_internal(mapping, entry_name, sizeof(T)));
}
/**
* @brief Map the entry of a mapping to a typed const reference
* @tparam T type of the mapped reference
* @return const reference to the memory zone containing the variable
* @see bind_physical_buffer()
*/
template <typename T>
const T& map_memory(const PDOMapping& mapping,
std::string_view entry_name) const {<--- Function parameter 'entry_name' should be passed by const reference. [+]Parameter 'entry_name' is passed by value. It could be passed as a const reference which is usually faster and recommended in C++.
return *reinterpret_cast<const T*>(
map_memory_internal(mapping, entry_name, sizeof(T)));
}
/**
* @brief Map a mapping to a typed reference
* @tparam T type of the mapped reference
* @return reference to the memory zone containing the mapping of type T
* @see bind_physical_buffer()
*/
template <typename T>
T& map_memory(const PDOMapping& mapping) {
return *reinterpret_cast<T*>(map_memory_internal(mapping, sizeof(T)));
}
/**
* @brief Map a mapping to a typed reference
* @tparam T type of the mapped reference
* @return reference to the memory zone containing the mapping of type T
* @see bind_physical_buffer()
*/
template <typename T>
const T& map_memory(const PDOMapping& mapping) const {
return *reinterpret_cast<const T*>(
map_memory_internal(mapping, sizeof(T)));
}
/**
* @brief Map the entire buffer to a typed reference
* @tparam T type of the mapped reference
* @return reference to the memory zone containing the variable of type T
* @see bind_physical_buffer()
*/
template <typename T, typename U, typename... Other>
std::tuple<T&, U&, Other&...> map_memory() {
return map_memory_tuple<T, U, Other...>(0);
}
/**
* @brief Map the entire buffer to a typed const reference
* @tparam T type of the mapped reference
* @return const reference to the memory zone containing the variable of
* type T
* @see bind_physical_buffer()
*/
template <typename T, typename U, typename... Other>
std::tuple<const T&, const U&, const Other&...> map_memory() const {
return map_memory_tuple<T, U, Other...>(0);
}
/**
* @brief Map the entire buffer to a typed reference
* @tparam T type of the mapped reference
* @return reference to the memory zone containing the variable of type T
* @see bind_physical_buffer()
*/
template <typename T>
T& map_memory() {
return map_memory<T>(access_mapping(0));
}
/**
* @brief Map the entire buffer to a typed rconst eference
* @tparam T type of the mapped reference
* @return const reference to the memory zone containing the variable of
* type T
* @see bind_physical_buffer()
*/
template <typename T>
const T& map_memory() const {
return map_memory<T>(access_mapping(0));
}
/**
* @brief Get size of the buffer in memory
* @details this function can be used when calling define_physical_buffer to
* set the adequate size
* @return the size of the buffer
*/
uint16_t memory_size() const;
/**
* @brief define the physical buffer of an ethercat slave
* @details only advantage using this function is to avoid problem with
* memory size. This function is intended to be called at unit device
* construction time
* @param eth_slave the target unit device
*/
void define_physical_buffer(SlaveDevice& eth_slave);
/**
* @brief Configure an ethercat unit device with the corresponding buffer
* and mappings
* @details this call is intended to be used in configuration
* function (configure_at_init()). If your code use predefined mappings
* and assignment simply do not call this function.
* @param eth_slave the unit device to configure
* @return true if configuration succeeded, false otherwise
* @see configure_at_init()
*/
bool configure(SlaveDevice& eth_slave) const;
/**
* @brief Configure an ethercat unit device with the corresponding assigned
* PDO mappings
* @details this call is intended to be used in configuration
* function (configure_at_init()). Copared to configure() this function only
* performs assignment but no mapping definition.
* @param eth_slave the unit device to configure
* @return true if configuration succeeded, false otherwise
* @see configure_at_init()
*/
bool assign(SlaveDevice& eth_slave) const;
/**
* @brief bind internal memory zone to the physical buffer of an ethercat
* slave
* @details this function must be called during call to master init()
* function. To do so, use it in init_step function of the device. The call
* to define_physical_buffer() is not mandatory as long as the physical
* buffer has been correctly defined with same parameters (addr and flags)
* as those used in this object. After this call all "map_memory" functions
* can be used.
* @param eth_slave the target unit device
* @see map_memory()
*/
void bind_physical_buffer(SlaveDevice& eth_slave);
};
using mapping_t = PDOMapping;
using buffer_t = PDOBuffer;
namespace cia402 {
// most useful conversion utilities
constexpr double rads2rpm = 60.0 / (2 * M_PI);
constexpr double rpm2rads = (2 * M_PI) / 60.0;
constexpr double rad2deg = 180. / M_PI;
constexpr double rev_to_rad = (2. * M_PI);
} // namespace cia402
} // namespace coe
} // namespace ethercatcpp
|