ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
iolink.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 iolink.h
6 * @brief IO-Link (SDCI, IEC 61131-9) data-link message codec (PROTOCORE_ENABLE_IOLINK).
7 *
8 * IO-Link is the point-to-point 3-wire serial link to smart sensors / actuators. This codec
9 * implements the data-link **message layer**: the M-sequence Control octet (MC), the
10 * checksum / M-sequence-type octet (CKT) of a master message, the checksum / status octet
11 * (CKS) of a device reply, and the SDCI message checksum that protects both directions.
12 *
13 * The checksum is the part everyone gets wrong, so it is implemented straight from the spec
14 * (IO-Link Interface and System Specification v1.1.4, Annex A.1.6): a 0x52 seed XORed octet
15 * by octet across the message (the check octet included with its checksum bits 0), then the
16 * 8-to-6-bit compression of equation (A.1). `protocore_iol_finalize` writes it into the check octet and
17 * `protocore_iol_verify` checks it.
18 *
19 * Scope: the message / DL layer. The per-type M-sequence octet layout (process + on-request
20 * data widths) is the device's profile, and the ISDU on-request service framing layers on top;
21 * lay those octets out per your device, then finalize / verify with this codec. The wire is a
22 * UART at 4.8 / 38.4 / 230.4 kbit/s through an IO-Link transceiver (e.g. MAX14819 / L6360);
23 * pure and host-tested.
24 *
25 * @author Douglas Quigg (dstroy0)
26 * @date 2026
27 */
28
29#ifndef PROTOCORE_IOLINK_H
30#define PROTOCORE_IOLINK_H
31
32#include "protocore_config.h" // the entry point: protocore_types.h for the widths
33
34#if PROTOCORE_ENABLE_IOLINK
35
37
38// This module holds nothing between calls, so it carves no borrow and states none. An entry
39// takes one all the same, and never reads it, so every namespace in the tree is invoked the
40// same way.
41
42#define IOL_CHECKSUM_SEED 0x52u ///< checksum seed XORed with the first octet (spec A.1.6)
43
44// M-sequence Control (MC) octet fields.
45#define IOL_MC_READ 0x80u ///< bit 7 set => read access
46#define IOL_MC_WRITE 0x00u ///< bit 7 clear => write access
47#define IOL_CH_PROCESS 0u ///< communication channel: Process Data
48#define IOL_CH_PAGE 1u ///< communication channel: Page (direct parameters)
49#define IOL_CH_DIAGNOSIS 2u ///< communication channel: Diagnosis
50#define IOL_CH_ISDU 3u ///< communication channel: ISDU (on-request data)
51
52// M-sequence types (CKT bits 7-6).
53#define IOL_MSEQ_TYPE_0 0u
54#define IOL_MSEQ_TYPE_1 1u
55#define IOL_MSEQ_TYPE_2 2u
56
57// Checksum / status (CKS) octet flags.
58#define IOL_CKS_EVENT 0x80u ///< bit 7: Device has an Event pending
59#define IOL_CKS_PD_INVALID 0x40u ///< bit 6: Process Data invalid
60
61#define IOL_CHECK_HIGH_MASK 0xC0u ///< the non-checksum (type / status) bits of a check octet
62#define IOL_CHECK_SUM_MASK 0x3Fu ///< the 6-bit checksum field of a check octet
63
64/** @brief What mc takes: read, channel, address. */
65typedef struct
66{
67 proto_bool read;
68 uint8_t channel;
69 uint8_t address;
70} IolinkMcArgs;
71
72/** @brief What mc_is_read takes: mc. */
73typedef struct
74{
75 uint8_t mc;
76} IolinkMcIsReadArgs;
77
78/** @brief What mc_channel takes: mc. */
79typedef struct
80{
81 uint8_t mc;
82} IolinkMcChannelArgs;
83
84/** @brief What mc_address takes: mc. */
85typedef struct
86{
87 uint8_t mc;
88} IolinkMcAddressArgs;
89
90/** @brief What ckt takes: mseq_type, checksum6. */
91typedef struct
92{
93 uint8_t mseq_type;
94 uint8_t checksum6;
95} IolinkCktArgs;
96
97/** @brief What cks takes: event, pd_invalid, checksum6. */
98typedef struct
99{
100 proto_bool event;
101 proto_bool pd_invalid;
102 uint8_t checksum6;
103} IolinkCksArgs;
104
105/** @brief What checksum6 takes: msg, len. */
106typedef struct
107{
108 const uint8_t *msg;
109 size_t len;
110} IolinkChecksum6Args;
111
112/** @brief What finalize takes: msg, len, check_idx. */
113typedef struct
114{
115 uint8_t *msg;
116 size_t len;
117 size_t check_idx;
118} IolinkFinalizeArgs;
119
120/** @brief What verify takes: msg, len, check_idx. */
121typedef struct
122{
123 const uint8_t *msg;
124 size_t len;
125 size_t check_idx;
126} IolinkVerifyArgs;
127
128/**
129 * @brief IO-Link (SDCI, IEC 61131-9) data-link message codec (PROTOCORE_ENABLE_IOLINK).
130 *
131 * A caller sets the members a call takes, invokes it through ::Iolink with the bytes it runs
132 * out of, and reads the outcome off the same handle.
133 *
134 * Iolink.mc_args.read = ...;
135 * Iolink.mc_args.channel = ...;
136 * Iolink.mc_args.address = ...;
137 * Iolink.mc(work);
138 * // Iolink.value is what the call reports
139 *
140 * @var IolinkNs::mc_args what mc takes: read, channel, address
141 * @var IolinkNs::mc_is_read_args what mc_is_read takes: mc
142 * @var IolinkNs::mc_channel_args what mc_channel takes: mc
143 * @var IolinkNs::mc_address_args what mc_address takes: mc
144 * @var IolinkNs::ckt_args what ckt takes: mseq_type, checksum6
145 * @var IolinkNs::cks_args what cks takes: event, pd_invalid, checksum6
146 * @var IolinkNs::checksum6_args what checksum6 takes: msg, len
147 * @var IolinkNs::finalize_args what finalize takes: msg, len, check_idx
148 * @var IolinkNs::verify_args what verify takes: msg, len, check_idx
149 * @var IolinkNs::ok a call's true/false outcome
150 * @var IolinkNs::value the value a call reports
151 * @var IolinkNs::mc build the M-sequence Control octet from access / channel / address ...
152 * @var IolinkNs::mc_is_read true if the MC octet requests a read
153 * @var IolinkNs::mc_channel communication channel from an MC octet (IOL_CH_*)
154 * @var IolinkNs::mc_address address (5-bit) from an MC octet
155 * @var IolinkNs::ckt build a CKT octet from an M-sequence type and a 6-bit checksum (use ...
156 * @var IolinkNs::cks build a CKS octet from the Event / PD-invalid flags and a 6-bit ...
157 * @var IolinkNs::checksum6 the compressed 6-bit SDCI checksum over msg (the check octet must ...
158 * @var IolinkNs::finalize finalize a message in place: compute the checksum over msg ...
159 * @var IolinkNs::verify verify a received message: recompute the checksum (masking off the ...
160 *
161 * @c work is bytes the CALLER holds. This module reads none of them: it carries nothing
162 * between calls, so there is no state to keep and nothing to wipe. The parameter is there so
163 * a caller drives every namespace the same way.
164 */
165typedef struct
166{
167 IolinkMcArgs mc_args;
168 IolinkMcIsReadArgs mc_is_read_args;
169 IolinkMcChannelArgs mc_channel_args;
170 IolinkMcAddressArgs mc_address_args;
171 IolinkCktArgs ckt_args;
172 IolinkCksArgs cks_args;
173 IolinkChecksum6Args checksum6_args;
174 IolinkFinalizeArgs finalize_args;
175 IolinkVerifyArgs verify_args;
176 proto_bool ok;
177 uint8_t value;
178} IolinkVars;
179
180/** @brief The operands and the outcome. */
181extern IolinkVars IolinkV;
182
183/** @brief The entries. */
184typedef struct
185{
186 void (*const mc)(uint8_t *work);
187 void (*const mc_is_read)(uint8_t *work);
188 void (*const mc_channel)(uint8_t *work);
189 void (*const mc_address)(uint8_t *work);
190 void (*const ckt)(uint8_t *work);
191 void (*const cks)(uint8_t *work);
192 void (*const checksum6)(uint8_t *work);
193 void (*const finalize)(uint8_t *work);
194 void (*const verify)(uint8_t *work);
195} IolinkNs;
196
197// What the table binds, defined once in the .c and taking one parameter each: everything
198// else an entry needs is an operand in IolinkV or a region of the borrow at a fixed offset.
199void protocore_iolink_mc(uint8_t *work);
200void protocore_iolink_mc_is_read(uint8_t *work);
201void protocore_iolink_mc_channel(uint8_t *work);
202void protocore_iolink_mc_address(uint8_t *work);
203void protocore_iolink_ckt(uint8_t *work);
204void protocore_iolink_cks(uint8_t *work);
205void protocore_iolink_checksum6(uint8_t *work);
206void protocore_iolink_finalize(uint8_t *work);
207void protocore_iolink_verify(uint8_t *work);
208
209// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
210// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
211// `Iolink.mc(work)` resolves to a named function and becomes a DIRECT call. An extern table
212// leaves the call indirect and the symbol live at every level, -O2 -flto included.
213static const IolinkNs Iolink __attribute__((unused)) = {
214 .mc = protocore_iolink_mc,
215 .mc_is_read = protocore_iolink_mc_is_read,
216 .mc_channel = protocore_iolink_mc_channel,
217 .mc_address = protocore_iolink_mc_address,
218 .ckt = protocore_iolink_ckt,
219 .cks = protocore_iolink_cks,
220 .checksum6 = protocore_iolink_checksum6,
221 .finalize = protocore_iolink_finalize,
222 .verify = protocore_iolink_verify,
223};
224
226
227#endif // PROTOCORE_ENABLE_IOLINK
228
229#endif // PROTOCORE_IOLINK_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