ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
sercos.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file sercos.h
6 * @brief SERCOS III motion-bus telegram + IDN codec (PROTOCORE_ENABLE_SERCOS).
7 *
8 * SERCOS III is the real-time drive/motion bus over Ethernet (raw L2, ethertype 0x88CD, on the shipped
9 * services/fieldbus/rawl2). The master cyclically sends **MDT** (Master Data Telegrams) carrying setpoints to the
10 * drives, and the drives answer with **AT** (Acknowledge / drive Telegrams) carrying actual values. Both
11 * carry a short SERCOS header then the cyclic device data; a separate **service channel** transfers
12 * parameters addressed by an **IDN** (IDentification Number):
13 *
14 * Telegram header: [type MDT/AT : 1][phase/counter : 1][cycle count : 2]
15 * IDN (16 bit): S/P bit(1) | parameter-set(3) | data-block(12) -> "S-0-0100" style addressing
16 *
17 * This provides the MDT/AT telegram framing + the IDN encode/decode (the addressing every drive
18 * parameter uses). The isochronous timing + the ring/line topology are the hardware-gated part. Pure,
19 * zero heap, no stdlib, host-testable.
20 */
21
22#ifndef PROTOCORE_SERCOS_H
23#define PROTOCORE_SERCOS_H
24
25#include "protocore_config.h" // the entry point: protocore_types.h for the widths
26
27#if PROTOCORE_ENABLE_SERCOS
28
30
31// This module holds nothing between calls, so it carves no borrow and states none. An entry
32// takes one all the same, and never reads it, so every namespace in the tree is invoked the
33// same way.
34
35// SERCOS telegram types + header length: wire values, so integer constants in a struct.
36#define SERCOS_TEL_MDT 0x00 ///< Master Data Telegram (master -> drives).
37#define SERCOS_TEL_AT 0x01 ///< Acknowledge Telegram (drive -> master).
38#define SERCOS_HDR_LEN 4
39
40/** @brief A parsed SERCOS telegram (data points into the input). */
41typedef struct
42{
43 uint8_t type;
44 uint8_t phase;
45 uint16_t cycle;
46 const uint8_t *data;
47 size_t data_len;
48} SercosTelegram;
49
50/** @brief What idn takes: is_product, param_set, data_block. */
51typedef struct
52{
53 proto_bool is_product; ///< true = a P-parameter (product-specific), false = an S-parameter (standard)
54 uint8_t param_set; ///< the parameter set 0..7
55 uint16_t data_block; ///< the data block number 0..4095
56} SercosIdnArgs;
57
58/** @brief What idn_parse takes: idn, is_product, param_set, data_block. */
59typedef struct
60{
61 uint16_t idn;
62 proto_bool *is_product;
63 uint8_t *param_set;
64 uint16_t *data_block;
65} SercosIdnParseArgs;
66
67/** @brief What build takes: type, phase, cycle, data, data_len, out, ... */
68typedef struct
69{
70 uint8_t type; ///< SERCOS_TEL_MDT or SERCOS_TEL_AT
71 uint8_t phase; ///< the communication phase / counter byte
72 uint16_t cycle; ///< the cycle count
73 const uint8_t *data; ///< the cyclic device data (may be null if data_len == 0)
74 size_t data_len;
75 uint8_t *out;
76 size_t cap;
77} SercosBuildArgs;
78
79/** @brief What parse takes: frame, len, out. */
80typedef struct
81{
82 const uint8_t *frame;
83 size_t len;
84 SercosTelegram *out;
85} SercosParseArgs;
86
87/**
88 * @brief SERCOS III motion-bus telegram + IDN codec (PROTOCORE_ENABLE_SERCOS).
89 *
90 * A caller sets the members a call takes, invokes it through ::Sercos with the bytes it runs
91 * out of, and reads the outcome off the same handle.
92 *
93 * Sercos.idn_args.is_product = ...;
94 * Sercos.idn_args.param_set = ...;
95 * Sercos.idn_args.data_block = ...;
96 * Sercos.idn(work);
97 * // Sercos.value is what the call reports
98 *
99 * @var SercosNs::idn_args what idn takes: is_product, param_set, data_block
100 * @var SercosNs::idn_parse_args what idn_parse takes: idn, is_product, param_set, data_block
101 * @var SercosNs::build_args what build takes: type, phase, cycle, data, data_len, out,
102 * @var SercosNs::parse_args what parse takes: frame, len, out
103 * @var SercosNs::ok a call's true/false outcome
104 * @var SercosNs::value the 16-bit IDN: bit15 = S/P, bits14..12 = set, bits11..0 = block
105 * @var SercosNs::n the telegram length (4 + data_len), or 0 on overflow
106 * @var SercosNs::idn encode a SERCOS IDN (16-bit) from its parts
107 * @var SercosNs::idn_parse decode a SERCOS IDN into its parts (any out-pointer may be null)
108 * @var SercosNs::build build a SERCOS telegram: [type][phase][cycle:2 LE][data...]
109 * @var SercosNs::parse parse a SERCOS telegram. true if len >= 4 and the type is MDT/AT
110 *
111 * @c work is bytes the CALLER holds. This module reads none of them: it carries nothing
112 * between calls, so there is no state to keep and nothing to wipe. The parameter is there so
113 * a caller drives every namespace the same way.
114 */
115typedef struct
116{
117 SercosIdnArgs idn_args;
118 SercosIdnParseArgs idn_parse_args;
119 SercosBuildArgs build_args;
120 SercosParseArgs parse_args;
121 proto_bool ok;
122 uint16_t value;
123 size_t n;
124} SercosVars;
125
126/** @brief The operands and the outcome. */
127extern SercosVars SercosV;
128
129/** @brief The entries. */
130typedef struct
131{
132 void (*const idn)(uint8_t *work);
133 void (*const idn_parse)(uint8_t *work);
134 void (*const build)(uint8_t *work);
135 void (*const parse)(uint8_t *work);
136} SercosNs;
137
138// What the table binds, defined once in the .c and taking one parameter each: everything
139// else an entry needs is an operand in SercosV or a region of the borrow at a fixed offset.
140void protocore_sercos_idn(uint8_t *work);
141void protocore_sercos_idn_parse(uint8_t *work);
142void protocore_sercos_build(uint8_t *work);
143void protocore_sercos_parse(uint8_t *work);
144
145// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
146// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
147// `Sercos.idn(work)` resolves to a named function and becomes a DIRECT call. An extern table
148// leaves the call indirect and the symbol live at every level, -O2 -flto included.
149static const SercosNs Sercos __attribute__((unused)) = {
150 .idn = protocore_sercos_idn,
151 .idn_parse = protocore_sercos_idn_parse,
152 .build = protocore_sercos_build,
153 .parse = protocore_sercos_parse,
154};
155
157
158#endif // PROTOCORE_ENABLE_SERCOS
159
160#endif // PROTOCORE_SERCOS_H
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97