GCC Code Coverage Report


Directory: ./
File: src/devices/Ex4xxx.cpp
Date: 2026-09-03 16:05:57
Exec Total Coverage
Lines: 65 111 58.6%
Branches: 29 262 11.1%

Line Branch Exec Source
1 /*****************************************************************************
2 *
3 * This file is part of the ecapp library (EtherCAT application devices).
4 *
5 * Copyright (C) 2026 Florian Pose <fp@igh.de>
6 *
7 * The ecapp library is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public License
9 * as published by the Free Software Foundation, version 3 of the
10 * License.
11 *
12 * The ecapp library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with the ecapp library. If not, see
19 * <https://www.gnu.org/licenses/>.
20 *
21 ****************************************************************************/
22
23 #include "devices/Ex4xxx.h"
24
25 #include "SlaveContext.h"
26 #include "ecapp/ConfigurableScale.h"
27 #include "ecapp/Domain.h"
28 #include "ecapp/Exceptions.h"
29 #include "ecapp/Factory.h"
30 #include "ecapp/Master.h"
31 #include "ecapp/SwappedSync.h"
32
33 #include <memory>
34 #include <sstream>
35 #include <stdexcept>
36
37 /****************************************************************************/
38
39 using std::make_unique;
40 using std::runtime_error;
41 using std::size_t;
42 using std::stringstream;
43 using std::unique_ptr;
44
45 namespace EcApp {
46
47 /* Master 0, Slave 2, "EL4022"
48 * Vendor ID: 0x00000002
49 * Product code: 0x0fb63052
50 * Revision number: 0x00140000
51 */
52
53 ec_pdo_entry_info_t el40x2_pdo_entries[] = {
54 {0x7000, 0x01, 16}, /* Analog output */
55 {0x7010, 0x01, 16}, /* Analog output */
56 };
57
58 ec_pdo_info_t el40x2_pdos[] = {
59 {0x1600, 1, el40x2_pdo_entries + 0}, /* AO RxPDO-Map Outputs Ch.1 */
60 {0x1601, 1, el40x2_pdo_entries + 1}, /* AO RxPDO-Map Outputs Ch.2 */
61 };
62
63 ec_sync_info_t el40x2_syncs[] = {
64 {0, EC_DIR_OUTPUT, 0, NULL, EC_WD_DISABLE},
65 {1, EC_DIR_INPUT, 0, NULL, EC_WD_DISABLE},
66 {2, EC_DIR_OUTPUT, 2, el40x2_pdos + 0, EC_WD_DISABLE},
67 {3, EC_DIR_INPUT, 0, NULL, EC_WD_DISABLE},
68 {0xff}};
69
70 /* Master 0, Slave 2, "EL4028"
71 * Vendor ID: 0x00000002
72 * Product code: 0x0fbc3052
73 * Revision number: 0x00140000
74 */
75
76 ec_pdo_entry_info_t el4028_pdo_entries[] = {
77 {0x7000, 0x01, 16}, /* Analog output */
78 {0x7010, 0x01, 16}, /* Analog output */
79 {0x7020, 0x01, 16}, /* Analog output */
80 {0x7030, 0x01, 16}, /* Analog output */
81 {0x7040, 0x01, 16}, /* Analog output */
82 {0x7050, 0x01, 16}, /* Analog output */
83 {0x7060, 0x01, 16}, /* Analog output */
84 {0x7070, 0x01, 16}, /* Analog output */
85 };
86
87 ec_pdo_info_t el4028_pdos[] = {
88 {0x1600, 1, el4028_pdo_entries + 0}, /* AO RxPDO-Map Outputs Ch.1 */
89 {0x1601, 1, el4028_pdo_entries + 1}, /* AO RxPDO-Map Outputs Ch.2 */
90 {0x1602, 1, el4028_pdo_entries + 2}, /* AO RxPDO-Map Outputs Ch.3 */
91 {0x1603, 1, el4028_pdo_entries + 3}, /* AO RxPDO-Map Outputs Ch.4 */
92 {0x1604, 1, el4028_pdo_entries + 4}, /* AO RxPDO-Map Outputs Ch.5 */
93 {0x1605, 1, el4028_pdo_entries + 5}, /* AO RxPDO-Map Outputs Ch.6 */
94 {0x1606, 1, el4028_pdo_entries + 6}, /* AO RxPDO-Map Outputs Ch.7 */
95 {0x1607, 1, el4028_pdo_entries + 7}, /* AO RxPDO-Map Outputs Ch.8 */
96 };
97
98 ec_sync_info_t el4028_syncs[] = {
99 {0, EC_DIR_OUTPUT, 0, NULL, EC_WD_DISABLE},
100 {1, EC_DIR_INPUT, 0, NULL, EC_WD_DISABLE},
101 {2, EC_DIR_OUTPUT, 8, el4028_pdos + 0, EC_WD_DISABLE},
102 {3, EC_DIR_INPUT, 0, NULL, EC_WD_DISABLE},
103 {0xff}};
104
105 /* Master 0, Slave 18, "EL4034"
106 * Vendor ID: 0x00000002
107 * Product code: 0x0fc23052
108 * Revision number: 0x00140000
109 */
110
111 ec_pdo_entry_info_t el4034_pdo_entries[] = {
112 {0x7000, 0x01, 16}, /* Analog output */
113 {0x7010, 0x01, 16}, /* Analog output */
114 {0x7020, 0x01, 16}, /* Analog output */
115 {0x7030, 0x01, 16}, /* Analog output */
116 };
117
118 ec_pdo_info_t el4034_pdos[] = {
119 {0x1600, 1, el4034_pdo_entries + 0}, /* RxPDO-Map OutputsCh.1 */
120 {0x1601, 1, el4034_pdo_entries + 1}, /* RxPDO-Map OutputsCh.2 */
121 {0x1602, 1, el4034_pdo_entries + 2}, /* RxPDO-Map OutputsCh.3 */
122 {0x1603, 1, el4034_pdo_entries + 3}, /* RxPDO-Map OutputsCh.4 */
123 };
124
125 ec_sync_info_t el4034_syncs[] = {
126 {0, EC_DIR_OUTPUT, 0, NULL, EC_WD_DISABLE},
127 {1, EC_DIR_INPUT, 0, NULL, EC_WD_DISABLE},
128 {2, EC_DIR_OUTPUT, 4, el4034_pdos + 0, EC_WD_DISABLE},
129 {3, EC_DIR_INPUT, 0, NULL, EC_WD_DISABLE},
130 {0xff}};
131
132 /* Master 0, Slave 15, "EL4102"
133 * Vendor ID: 0x00000002
134 * Product code: 0x10063052
135 * Revision number: 0x03fc0000
136 */
137
138 ec_pdo_entry_info_t el4102_pdo_entries[] = {
139 {0x3001, 0x01, 16}, /* Output */
140 {0x3002, 0x01, 16}, /* Output */
141 };
142
143 ec_pdo_info_t el4102_pdos[] = {
144 {0x1600, 1, el4102_pdo_entries + 0}, /* RxPDO 01 mapping */
145 {0x1601, 1, el4102_pdo_entries + 1}, /* RxPDO 02 mapping */
146 };
147
148 ec_sync_info_t el4102_syncs[] = {
149 {0, EC_DIR_OUTPUT, 0, NULL, EC_WD_DISABLE},
150 {1, EC_DIR_INPUT, 0, NULL, EC_WD_DISABLE},
151 {2, EC_DIR_OUTPUT, 2, el4102_pdos + 0, EC_WD_DISABLE},
152 {3, EC_DIR_INPUT, 0, NULL, EC_WD_DISABLE},
153 {0xff}};
154
155 /* Master 0, Slave 5, "EL4134"
156 * Vendor ID: 0x00000002
157 * Product code: 0x10263052
158 * Revision number: 0x03fc0000
159 */
160
161 ec_pdo_entry_info_t el4134_pdo_entries[] = {
162 {0x7000, 0x01, 16}, /* Analog output */
163 {0x7010, 0x01, 16}, /* Analog output */
164 {0x7020, 0x01, 16}, /* Analog output */
165 {0x7030, 0x01, 16}, /* Analog output */
166 };
167
168 ec_pdo_info_t el4134_pdos[] = {
169 {0x1600, 1, el4134_pdo_entries + 0}, /* AO RxPDO-Map OutputsCh.1 */
170 {0x1601, 1, el4134_pdo_entries + 1}, /* AO RxPDO-Map OutputsCh.2 */
171 {0x1602, 1, el4134_pdo_entries + 2}, /* AO RxPDO-Map OutputsCh.3 */
172 {0x1603, 1, el4134_pdo_entries + 3}, /* AO RxPDO-Map OutputsCh.4 */
173 };
174
175 ec_sync_info_t el4134_syncs[] = {
176 {0, EC_DIR_OUTPUT, 0, NULL, EC_WD_DISABLE},
177 {1, EC_DIR_INPUT, 0, NULL, EC_WD_DISABLE},
178 {2, EC_DIR_OUTPUT, 4, el4134_pdos + 0, EC_WD_DISABLE},
179 {3, EC_DIR_INPUT, 0, NULL, EC_WD_DISABLE},
180 {0xff}};
181
182 /****************************************************************************/
183
184 struct Ex4xxxType
185 {
186 const char *name;
187 uint32_t product_code;
188 unsigned int num_channels;
189 uint16_t entry_index;
190 uint16_t index_inc;
191 ec_sync_info_t *sync;
192 };
193
194 const static Ex4xxxType ex4xxxType[Ex4xxx<Mode::Control>::NumTypes] = {
195 {"EL4002", 0x0fa23052, 2, 0x7000, 0x0010, el40x2_syncs},
196 {"EL4012", 0x0fac3052, 2, 0x7000, 0x0010, el40x2_syncs},
197 {"EL4022", 0x0fb63052, 2, 0x7000, 0x0010, el40x2_syncs},
198 {"EL4028", 0x0fbc3052, 8, 0x7000, 0x0010, el4028_syncs},
199 {"EL4034", 0x0fc23052, 4, 0x7000, 0x0010, el4034_syncs},
200 {"EL4102", 0x10063052, 2, 0x3001, 0x0001, el4102_syncs},
201 {"EL4134", 0x10263052, 4, 0x7000, 0x0010, el4134_syncs},
202 };
203
204 /****************************************************************************/
205
206 template <Mode mode>
207 1 Ex4xxx<mode>::Ex4xxx(
208 pdserv *pdserv,
209 pdtask *task,
210 const std::string &prefix,
211 Master *master,
212 uint16_t alias,
213 uint16_t position,
214 Domain *domainOut,
215 Type typeIdx) :
216 1 type(ex4xxxType[typeIdx]),
217 sc(NULL),
218 domainOut(domainOut),
219 1 off_out(type.num_channels, 0),
220 1 fullScale(type.num_channels, 1.0),
221 1 input(type.num_channels, 0.0),
222 1 enableForcing(type.num_channels, 0),
223 1 forcedValue(type.num_channels, 0.0),
224
6/24
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 7 taken 1 times.
✗ Branch 8 not taken.
✓ Branch 12 taken 1 times.
✗ Branch 13 not taken.
✓ Branch 17 taken 1 times.
✗ Branch 18 not taken.
✓ Branch 22 taken 1 times.
✗ Branch 23 not taken.
✓ Branch 27 taken 1 times.
✗ Branch 28 not taken.
✗ Branch 45 not taken.
✗ Branch 46 not taken.
✗ Branch 50 not taken.
✗ Branch 51 not taken.
✗ Branch 55 not taken.
✗ Branch 56 not taken.
✗ Branch 60 not taken.
✗ Branch 61 not taken.
✗ Branch 65 not taken.
✗ Branch 66 not taken.
✗ Branch 70 not taken.
✗ Branch 71 not taken.
6 output(type.num_channels, 0.0)
225 {
226
1/8
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
1 if (pdserv && task) {
227 pdserv_parameter(
228 pdserv, (prefix + "/FullScale").c_str(), 0666, pd_double_T,
229 fullScale.data(), fullScale.size(), NULL, NULL, NULL);
230 pdserv_signal(
231 task, 1, (prefix + "/Input").c_str(), pd_double_T,
232 input.data(), input.size(), NULL);
233 pdserv_parameter(
234 pdserv, (prefix + "/EnableForcing").c_str(), 0666,
235 pd_boolean_T, enableForcing.data(), enableForcing.size(),
236 NULL, NULL, NULL);
237 pdserv_parameter(
238 pdserv, (prefix + "/ForcedValue").c_str(), 0666, pd_double_T,
239 forcedValue.data(), forcedValue.size(), NULL, NULL, NULL);
240 pdserv_signal(
241 task, 1, (prefix + "/Output").c_str(), pd_double_T,
242 output.data(), output.size(), NULL);
243 }
244
245
1/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1 if (!master) {
246 1 return;
247 }
248
249 sc = ecrt_master_slave_config(
250 master->getMaster(), alias, position, 0x00000002,
251 type.product_code);
252 if (!sc) {
253 stringstream err;
254 err << "Failed to get slave configuration.";
255 throw runtime_error(err.str());
256 }
257
258 if (ecrt_slave_config_pdos(
259 sc, EC_END, SwappedSync<mode>(type.sync).getSync())) {
260 stringstream err;
261 err << "Failed to configure PDOs.";
262 throw runtime_error(err.str());
263 }
264
265 for (unsigned int c = 0; c < type.num_channels; c++) {
266 unsigned int entry_index = type.entry_index + type.index_inc * c;
267 off_out[c] = ecrt_slave_config_reg_pdo_entry(
268 sc, entry_index, 0x01, domainOut->getDomain(), NULL);
269 if (off_out[c] < 0) {
270 stringstream err;
271 err << "Failed to register Ex4xxx value PDO entry.";
272 throw runtime_error(err.str());
273 }
274 }
275 }
276
277 /****************************************************************************/
278
279 template <Mode mode>
280 1 void Ex4xxx<mode>::setFullScale(unsigned int channel, double newScale)
281 {
282
1/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1 if (channel < type.num_channels) {
283 1 fullScale[channel] = newScale;
284 }
285 1 }
286
287 /****************************************************************************/
288
289 template <Mode mode>
290 3 void Ex4xxx<mode>::setOutput(unsigned int channel, double extValue)
291 {
292
1/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
3 if (channel < type.num_channels) {
293 3 input[channel] = extValue;
294 }
295 3 }
296
297 /****************************************************************************/
298
299 template <Mode mode>
300 double Ex4xxx<mode>::getOutput(unsigned int channel)
301 {
302 if (domainOut->getData() && channel < type.num_channels) {
303 int16_t raw = EC_READ_S16(domainOut->getData() + off_out[channel]);
304 return raw * fullScale[channel] / 32768.0;
305 }
306 else {
307 return 0.0;
308 }
309 }
310
311 /****************************************************************************/
312
313 template <Mode mode>
314 1 void Ex4xxx<mode>::update()
315 {
316
2/4
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
3 for (unsigned int channel = 0; channel < type.num_channels; channel++) {
317
1/4
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
2 output[channel] = enableForcing[channel] ? forcedValue[channel]
318 : input[channel];
319
320
1/4
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
2 if (domainOut->getData()) {
321 double raw;
322
323 if (fullScale[channel]) {
324 raw = output[channel] / fullScale[channel] * 32768.0;
325 }
326 else {
327 raw = 0;
328 }
329
330 int16_t out;
331 if (raw > 32767.0) {
332 out = 32767;
333 }
334 else if (raw < -32768) {
335 out = -32768;
336 }
337 else {
338 out = raw;
339 }
340
341 EC_WRITE_S16(domainOut->getData() + off_out[channel], out);
342 }
343 }
344 1 }
345
346 /****************************************************************************/
347
348 template <Mode mode>
349 1 unsigned int Ex4xxx<mode>::numChannels() const
350 {
351 1 return type.num_channels;
352 }
353
354 /****************************************************************************/
355
356 template class Ex4xxx<Mode::Control>;
357 template class Ex4xxx<Mode::Simulation>;
358
359 /****************************************************************************/
360 // Generic SubDevice adapter: pure analog output, no inputs. Application
361 // code never names Ex4xxx, only e.g. "EL4022" -- the factory resolves
362 // it.
363 /****************************************************************************/
364
365 template <Mode mode>
366 2 class Ex4xxxAdapter : public SubDevice, public ConfigurableScale
367 {
368 public:
369 1 Ex4xxxAdapter(const SlaveContext &ctx, typename Ex4xxx<mode>::Type t) :
370 1 device(ctx.pdServ,
371 1 ctx.task,
372 ctx.prefix,
373 1 ctx.master,
374 1 ctx.alias,
375 1 ctx.position,
376 1 ctx.domainOut,
377 t),
378
6/24
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
✓ Branch 8 taken 1 times.
✗ Branch 9 not taken.
✓ Branch 11 taken 1 times.
✗ Branch 12 not taken.
✓ Branch 15 taken 1 times.
✗ Branch 16 not taken.
✓ Branch 18 taken 1 times.
✓ Branch 19 taken 1 times.
✗ Branch 23 not taken.
✗ Branch 24 not taken.
✗ Branch 34 not taken.
✗ Branch 35 not taken.
✗ Branch 39 not taken.
✗ Branch 40 not taken.
✗ Branch 42 not taken.
✗ Branch 43 not taken.
✗ Branch 46 not taken.
✗ Branch 47 not taken.
✗ Branch 49 not taken.
✗ Branch 50 not taken.
✗ Branch 54 not taken.
✗ Branch 55 not taken.
5 outputKinds_ {{"Output", device.numChannels(), double(0)}}
379 1 {}
380
381 1 void updateInputs() override {}
382 1 void updateOutputs() override { device.update(); }
383
384 2 const std::vector<ChannelKind> &inputKinds() const override
385 {
386 2 return inputKinds_;
387 }
388 10 const std::vector<ChannelKind> &outputKinds() const override
389 {
390 10 return outputKinds_;
391 }
392
393 1 void setScale(unsigned int channel, double scale) override
394 {
395 1 device.setFullScale(channel, scale);
396 1 }
397
398 /** Ex4xxx has no offset concept -- a legitimate no-op. */
399 1 void setOffset(unsigned int, double) override {}
400
401 protected:
402 ChannelValue getInputAt(size_t) const override
403 {
404 throw UnknownChannel("Ex4xxx has no input channels.");
405 }
406 4 void setOutputAt(size_t i, const ChannelValue &value) override
407 {
408 7 device.setOutput(
409 4 static_cast<unsigned int>(i), std::get<double>(value));
410 3 }
411
412 private:
413 Ex4xxx<mode> device;
414 std::vector<ChannelKind> inputKinds_;
415 std::vector<ChannelKind> outputKinds_;
416 };
417
418 /****************************************************************************/
419
420 namespace {
421
422 56 DeviceFactoryFn makeEx4xxxFactory(unsigned int typeIdx)
423 {
424 2 return [typeIdx](const SlaveContext &ctx) -> unique_ptr<SubDevice> {
425
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (ctx.mode == Mode::Control) {
426 return make_unique<Ex4xxxAdapter<Mode::Control>>(
427 ctx, static_cast<Ex4xxx<Mode::Control>::Type>(typeIdx));
428 }
429 else {
430
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
2 return make_unique<Ex4xxxAdapter<Mode::Simulation>>(
431 ctx,
432 3 static_cast<Ex4xxx<Mode::Simulation>::Type>(typeIdx));
433 }
434 56 };
435 }
436
437 struct Ex4xxxRegistrations
438 {
439 8 Ex4xxxRegistrations()
440 {
441
2/2
✓ Branch 0 taken 56 times.
✓ Branch 1 taken 8 times.
64 for (unsigned int i = 0; i < Ex4xxx<Mode::Control>::NumTypes; i++) {
442 56 const auto &t = ex4xxxType[i];
443
4/8
✓ Branch 3 taken 56 times.
✗ Branch 4 not taken.
✓ Branch 7 taken 56 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 56 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 56 times.
✗ Branch 14 not taken.
280 Registry::instance().add(
444 112 {t.name, __FILE__, 0x00000002, t.product_code,
445
1/2
✓ Branch 1 taken 56 times.
✗ Branch 2 not taken.
112 AnyRevision, wrapFactory(makeEx4xxxFactory(i))});
446 }
447 8 }
448 8 } ex4xxxRegistrations;
449
450 } // namespace
451
452 24 } // namespace EcApp
453
454 /****************************************************************************/
455