ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
devicenet.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 devicenet.h
6 * @brief DeviceNet link-adaptation codec (PROTOCORE_ENABLE_DEVICENET) - the CAN-specific layer of
7 * "CIP over CAN".
8 *
9 * DeviceNet (ODVA) carries CIP over classic CAN. The CIP application layer (services, EPATH,
10 * data) is the same one the EtherNet/IP codec uses, so build the message body with the
11 * existing `Cip.*` functions (`PROTOCORE_ENABLE_CIP`); this module supplies the DeviceNet-specific
12 * link adaptation that is NOT part of CIP:
13 *
14 * - The 11-bit CAN **identifier** as a Message Group (1..4) + Message ID + MAC ID, per the
15 * DeviceNet identifier allocation:
16 * @code
17 * Group 1: 0 MsgID(4) SourceMAC(6) ids 0x000-0x3FF
18 * Group 2: 10 MAC(6) MsgID(3) ids 0x400-0x5FF
19 * Group 3: 11 MsgID(3) SourceMAC(6) ids 0x600-0x7BF
20 * Group 4: 11111 MsgID(6) ids 0x7C0-0x7EF
21 * @endcode
22 * - The explicit-message **header octet** (FRAG | XID | MAC ID).
23 * - The **fragmentation protocol** (type + modulo-64 count) and a reassembler for explicit
24 * messages longer than one 8-octet frame.
25 *
26 * Pure and host-tested. Drive it from the ESP32 TWAI peripheral or an MCP2515 over SPI to
27 * bridge a DeviceNet segment onto Wi-Fi.
28 *
29 * @author Douglas Quigg (dstroy0)
30 * @date 2026
31 */
32
33#ifndef PROTOCORE_DEVICENET_H
34#define PROTOCORE_DEVICENET_H
35
36#include "protocore_config.h" // the entry point: protocore_types.h for the widths
37
38#if PROTOCORE_ENABLE_DEVICENET
39
41
42// This module holds nothing between calls, so it carves no borrow and states none. An entry
43// takes one all the same, and never reads it, so every namespace in the tree is invoked the
44// same way.
45
46// Message-group identifier bases / field widths.
47#define DEVICENET_G1_BASE 0x000u ///< Message Group 1 (0x000-0x3FF)
48#define DEVICENET_G2_BASE 0x400u ///< Message Group 2 (0x400-0x5FF)
49#define DEVICENET_G3_BASE 0x600u ///< Message Group 3 (0x600-0x7BF)
50#define DEVICENET_G4_BASE 0x7C0u ///< Message Group 4 (0x7C0-0x7EF)
51#define DEVICENET_MAC_MASK 0x3Fu ///< MAC IDs are 0..63
52
53// Common Group 2 message IDs (predefined master/slave connection set).
54#define DEVICENET_G2_UNCONNECTED_EXPLICIT_REQ 4u ///< unconnected explicit request to a slave
55#define DEVICENET_G2_EXPLICIT_RESPONSE 3u ///< explicit / unconnected response from a slave
56#define DEVICENET_G2_POLL_COMMAND 5u ///< Poll command / change-of-state to a slave
57#define DEVICENET_G2_DUP_MAC_CHECK 7u ///< Duplicate MAC ID check
58
59// Explicit-message header octet fields.
60#define DEVICENET_HDR_FRAG 0x80u ///< this body is fragmented (a fragmentation octet follows)
61#define DEVICENET_HDR_XID 0x40u ///< transaction-id bit
62
63// Fragmentation octet: type in the top 2 bits, modulo-64 count in the low 6.
64#define DEVICENET_FRAG_FIRST 0x00u ///< first fragment
65#define DEVICENET_FRAG_MIDDLE 0x40u ///< middle fragment
66#define DEVICENET_FRAG_LAST 0x80u ///< last fragment
67#define DEVICENET_FRAG_ACK 0xC0u ///< fragment acknowledge
68#define DEVICENET_FRAG_TYPE_MASK 0xC0u
69#define DEVICENET_FRAG_COUNT_MASK 0x3Fu
70
71/** @brief DeviceNet message groups. */
72typedef enum PROTO_ENUM_PACKED
73{
74 DEVICENET_GROUP_1 = 1,
75 DEVICENET_GROUP_2 = 2,
76 DEVICENET_GROUP_3 = 3,
77 DEVICENET_GROUP_4 = 4,
78} DeviceNetGroup;
79
80/** @brief A decoded DeviceNet identifier. */
81typedef struct
82{
83 DeviceNetGroup group;
84 uint8_t msg_id; ///< message id within the group
85 uint8_t mac_id; ///< source / node MAC id (0..63; not present for Group 4)
86} DeviceNetId;
87
88/** @brief Result of feeding a frame to the fragmentation reassembler. */
89typedef enum PROTO_ENUM_PACKED
90{
91 DEVICENET_FRAG_IGNORED = 0,
92 DEVICENET_FRAG_STARTED,
93 DEVICENET_FRAG_PROGRESS,
94 DEVICENET_FRAG_COMPLETE,
95 DEVICENET_FRAG_ERR,
96} DeviceNetFragResult;
97
98/** @brief Fragmented-message reassembly context. */
99typedef struct
100{
101 proto_bool active;
102 uint8_t next_count; ///< next expected modulo-64 fragment count
103 uint16_t len; ///< octets stored so far
104 uint8_t buf[PROTOCORE_DEVICENET_MSG_MAX]; ///< reassembled body (excludes the fragmentation octets)
105} DeviceNetFragRx;
106
107#include "shared/can/can.h" // CanFrame: the type a parameter points at
108
109/** @brief What encode_id takes: id, group, msg_id, mac_id. */
110typedef struct
111{
112 uint32_t *id;
113 DeviceNetGroup group;
114 uint8_t msg_id;
115 uint8_t mac_id;
116} DevicenetEncodeIdArgs;
117
118/** @brief What decode_id takes: can_id, out. */
119typedef struct
120{
121 uint32_t can_id;
122 DeviceNetId *out;
123} DevicenetDecodeIdArgs;
124
125/** @brief What msg_header takes: frag, xid, mac_id. */
126typedef struct
127{
128 proto_bool frag;
129 proto_bool xid;
130 uint8_t mac_id;
131} DevicenetMsgHeaderArgs;
132
133/** @brief What frag_octet takes: type, count. */
134typedef struct
135{
136 uint8_t type;
137 uint8_t count;
138} DevicenetFragOctetArgs;
139
140/** @brief What build_explicit takes: out, group, msg_id, mac_id, ... */
141typedef struct
142{
143 CanFrame *out;
144 DeviceNetGroup group;
145 uint8_t msg_id;
146 uint8_t mac_id;
147 const uint8_t *body;
148 uint8_t body_len;
149} DevicenetBuildExplicitArgs;
150
151/** @brief What build_fragment takes: out, group, msg_id, mac_id, xid, ... */
152typedef struct
153{
154 CanFrame *out;
155 DeviceNetGroup group;
156 uint8_t msg_id;
157 uint8_t mac_id;
158 proto_bool xid;
159 uint8_t frag_type;
160 uint8_t frag_count;
161 const uint8_t *data;
162 uint8_t data_len;
163} DevicenetBuildFragmentArgs;
164
165/** @brief What frag_reset takes: rx. */
166typedef struct
167{
168 DeviceNetFragRx *rx;
169} DevicenetFragResetArgs;
170
171/** @brief What frag_feed takes: rx, body, body_len. */
172typedef struct
173{
174 DeviceNetFragRx *rx;
175 const uint8_t *body;
176 uint8_t body_len;
177} DevicenetFragFeedArgs;
178
179/**
180 * @brief DeviceNet link-adaptation codec (PROTOCORE_ENABLE_DEVICENET) - the CAN-specific layer of "CIP over CAN".
181 * DeviceNet (ODVA) carries CIP over classic CAN.
182 *
183 * A caller sets the members a call takes, invokes it through ::Devicenet with the bytes it runs
184 * out of, and reads the outcome off the same handle.
185 *
186 * Devicenet.encode_id_args.id = ...;
187 * Devicenet.encode_id_args.group = ...;
188 * Devicenet.encode_id_args.msg_id = ...;
189 * Devicenet.encode_id_args.mac_id = ...;
190 * Devicenet.encode_id(work);
191 * // Devicenet.ok is what the call reports
192 *
193 * @var DevicenetNs::encode_id_args what encode_id takes: id, group, msg_id, mac_id
194 * @var DevicenetNs::decode_id_args what decode_id takes: can_id, out
195 * @var DevicenetNs::msg_header_args what msg_header takes: frag, xid, mac_id
196 * @var DevicenetNs::frag_octet_args what frag_octet takes: type, count
197 * @var DevicenetNs::build_explicit_args what build_explicit takes: out, group, msg_id, mac_id,
198 * @var DevicenetNs::build_fragment_args what build_fragment takes: out, group, msg_id, mac_id, xid,
199 * @var DevicenetNs::frag_reset_args what frag_reset takes: rx
200 * @var DevicenetNs::frag_feed_args what frag_feed takes: rx, body, body_len
201 * @var DevicenetNs::ok true on success; false on a null out, data_len > 6, a null data ...
202 * @var DevicenetNs::value the value a call reports
203 * @var DevicenetNs::frag what a call reports
204 * @var DevicenetNs::encode_id encode a DeviceNet 11-bit CAN id. mac_id is ignored for Group 4
205 * @var DevicenetNs::decode_id decode an 11-bit CAN id into its DeviceNet group / message id / MAC ...
206 * @var DevicenetNs::msg_header compose the explicit-message header octet (FRAG / XID / MAC id)
207 * @var DevicenetNs::frag_octet compose a fragmentation octet from a type (DEVICENET_FRAG_*) and a ...
208 * @var DevicenetNs::build_explicit build a single-frame explicit message: [header octet][body...] at ...
209 * @var DevicenetNs::build_fragment build one fragment of a fragmented explicit message (the sender ...
210 * @var DevicenetNs::frag_reset reset a reassembly context to idle
211 * @var DevicenetNs::frag_feed feed a received frame's body (the octets after the CAN id) to the ...
212 *
213 * @c work is bytes the CALLER holds. This module reads none of them: it carries nothing
214 * between calls, so there is no state to keep and nothing to wipe. The parameter is there so
215 * a caller drives every namespace the same way.
216 */
217typedef struct
218{
219 DevicenetEncodeIdArgs encode_id_args;
220 DevicenetDecodeIdArgs decode_id_args;
221 DevicenetMsgHeaderArgs msg_header_args;
222 DevicenetFragOctetArgs frag_octet_args;
223 DevicenetBuildExplicitArgs build_explicit_args;
224 DevicenetBuildFragmentArgs build_fragment_args;
225 DevicenetFragResetArgs frag_reset_args;
226 DevicenetFragFeedArgs frag_feed_args;
227 proto_bool ok;
228 uint8_t value;
229 DeviceNetFragResult frag;
230} DevicenetVars;
231
232/** @brief The operands and the outcome. */
233extern DevicenetVars DevicenetV;
234
235/** @brief The entries. */
236typedef struct
237{
238 void (*const encode_id)(uint8_t *work);
239 void (*const decode_id)(uint8_t *work);
240 void (*const msg_header)(uint8_t *work);
241 void (*const frag_octet)(uint8_t *work);
242 void (*const build_explicit)(uint8_t *work);
243 void (*const build_fragment)(uint8_t *work);
244 void (*const frag_reset)(uint8_t *work);
245 void (*const frag_feed)(uint8_t *work);
246} DevicenetNs;
247
248// What the table binds, defined once in the .c and taking one parameter each: everything
249// else an entry needs is an operand in DevicenetV or a region of the borrow at a fixed offset.
250void protocore_devicenet_encode_id(uint8_t *work);
251void protocore_devicenet_decode_id(uint8_t *work);
252void protocore_devicenet_msg_header(uint8_t *work);
253void protocore_devicenet_frag_octet(uint8_t *work);
254void protocore_devicenet_build_explicit(uint8_t *work);
255void protocore_devicenet_build_fragment(uint8_t *work);
256void protocore_devicenet_frag_reset(uint8_t *work);
257void protocore_devicenet_frag_feed(uint8_t *work);
258
259// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
260// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
261// `Devicenet.encode_id(work)` resolves to a named function and becomes a DIRECT call. An extern table
262// leaves the call indirect and the symbol live at every level, -O2 -flto included.
263static const DevicenetNs Devicenet __attribute__((unused)) = {
264 .encode_id = protocore_devicenet_encode_id,
265 .decode_id = protocore_devicenet_decode_id,
266 .msg_header = protocore_devicenet_msg_header,
267 .frag_octet = protocore_devicenet_frag_octet,
268 .build_explicit = protocore_devicenet_build_explicit,
269 .build_fragment = protocore_devicenet_build_fragment,
270 .frag_reset = protocore_devicenet_frag_reset,
271 .frag_feed = protocore_devicenet_frag_feed,
272};
273
275
276#endif // PROTOCORE_ENABLE_DEVICENET
277
278#endif // PROTOCORE_DEVICENET_H
PROTO_ENUM_PACKED
Application protocol spoken on a listener port or connection slot.
Shared CAN 2.0 frame type for the CAN-based industrial codecs (one source of truth).
Definition can.h:44
#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