ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
profibus.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 profibus.h
6 * @brief PROFIBUS-DP FDL telegram codec (PROTOCORE_ENABLE_PROFIBUS).
7 *
8 * PROFIBUS-DP is the Siemens RS-485 master/slave fieldbus (the DP-V0 cyclic I/O exchange). Its FDL data
9 * link uses fixed telegram formats delimited by a start byte (SD):
10 *
11 * - **SD1 (0x10)**: no data - `SD1 DA SA FC FCS ED` (a request/status telegram).
12 * - **SD2 (0x68)**: variable data - `SD2 LE LEr SD2 DA SA FC [data...] FCS ED`, where LE = length of
13 * (DA + SA + FC + data), repeated as LEr for redundancy.
14 * - **SD3 (0xA2)**: fixed 8 data bytes (not built here).
15 *
16 * DA = destination, SA = source, FC = frame control. The FCS is the arithmetic sum (mod 256) of DA + SA
17 * + FC + data. ED (end delimiter) is 0x16. This builds/validates the SD1 and SD2 telegrams a DP master
18 * exchanges with slaves; the RS-485 UART timing + the DP-V0 state machine are the device step. Pure,
19 * zero heap, no stdlib, host-testable.
20 */
21
22#ifndef PROTOCORE_PROFIBUS_H
23#define PROTOCORE_PROFIBUS_H
24
25#include "protocore_config.h" // the entry point: protocore_types.h for the widths
26
27#if PROTOCORE_ENABLE_PROFIBUS
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// PROFIBUS telegram delimiters + Frame Control values.
36#define PB_SD1 0x10 ///< start delimiter: no data.
37#define PB_SD2 0x68 ///< start delimiter: variable data.
38#define PB_SD3 0xA2 ///< start delimiter: fixed 8 data.
39#define PB_SD4 0xDC ///< token telegram.
40#define PB_ED 0x16 ///< end delimiter.
41
42// Frame Control (FC) common values.
43#define PB_FC_REQUEST_FDL_STATUS 0x49 ///< request FDL status: function code 9, request frame, FCB 0, FCV 0.
44#define PB_FC_SRD_LOW 0x6C ///< Send and Request Data low: function code 12, request frame, FCB 1, FCV 0.
45#define PB_FC_SRD_HIGH 0x7D ///< Send and Request Data high: function code 13, request frame, FCB 1, FCV 1.
46
47/** @brief A parsed PROFIBUS telegram (data points into the input, null for SD1). */
48typedef struct
49{
50 uint8_t sd; ///< the start delimiter (PB_SD1 / PB_SD2 / PB_SD3).
51 uint8_t da;
52 uint8_t sa;
53 uint8_t fc;
54 const uint8_t *data;
55 size_t data_len;
56} PbTelegram;
57
58/** @brief What fcs takes: bytes, len. */
59typedef struct
60{
61 const uint8_t *bytes;
62 size_t len;
63} ProfibusFcsArgs;
64
65/** @brief What build_sd1 takes: da, sa, fc, out, cap. */
66typedef struct
67{
68 uint8_t da;
69 uint8_t sa;
70 uint8_t fc;
71 uint8_t *out;
72 size_t cap;
73} ProfibusBuildSd1Args;
74
75/** @brief What build_sd2 takes: da, sa, fc, data, data_len, out, cap. */
76typedef struct
77{
78 uint8_t da;
79 uint8_t sa;
80 uint8_t fc;
81 const uint8_t *data; ///< the data unit
82 size_t data_len; ///< 1..246 (the DP process data); SD1 carries a telegram with no data field
83 uint8_t *out;
84 size_t cap;
85} ProfibusBuildSd2Args;
86
87/** @brief What build_sd3 takes: da, sa, fc, data, out, cap. */
88typedef struct
89{
90 uint8_t da;
91 uint8_t sa;
92 uint8_t fc;
93 const uint8_t *data;
94 uint8_t *out;
95 size_t cap;
96} ProfibusBuildSd3Args;
97
98/** @brief What parse takes: frame, len, out. */
99typedef struct
100{
101 const uint8_t *frame;
102 size_t len;
103 PbTelegram *out;
104} ProfibusParseArgs;
105
106/**
107 * @brief PROFIBUS-DP FDL telegram codec (PROTOCORE_ENABLE_PROFIBUS).
108 *
109 * A caller sets the members a call takes, invokes it through ::Profibus with the bytes it runs
110 * out of, and reads the outcome off the same handle.
111 *
112 * Profibus.fcs_args.bytes = ...;
113 * Profibus.fcs_args.len = ...;
114 * Profibus.fcs(work);
115 * // Profibus.value is what the call reports
116 *
117 * @var ProfibusNs::fcs_args what fcs takes: bytes, len
118 * @var ProfibusNs::build_sd1_args what build_sd1 takes: da, sa, fc, out, cap
119 * @var ProfibusNs::build_sd2_args what build_sd2 takes: da, sa, fc, data, data_len, out, cap
120 * @var ProfibusNs::build_sd3_args what build_sd3 takes: da, sa, fc, data, out, cap
121 * @var ProfibusNs::parse_args what parse takes: frame, len, out
122 * @var ProfibusNs::ok a call's true/false outcome
123 * @var ProfibusNs::value the value a call reports
124 * @var ProfibusNs::n the telegram length (6 + 3 + data_len... = 9 + data_len), or 0 on ...
125 * @var ProfibusNs::fcs PROFIBUS FCS: arithmetic sum (mod 256) of len bytes (DA + SA + FC + ...
126 * @var ProfibusNs::build_sd1 build an SD1 (no-data) telegram: SD1 DA SA FC FCS ED. 6, or 0 on ...
127 * @var ProfibusNs::build_sd2 build an SD2 (variable-data) telegram: SD2 LE LEr SD2 DA SA FC data ...
128 * @var ProfibusNs::build_sd3 build an SD3 (fixed 8-octet data) telegram: SD3 DA SA FC data[8] ...
129 * @var ProfibusNs::parse validate + parse an SD1 / SD2 / SD3 telegram (FCS + ED checked). ...
130 *
131 * @c work is bytes the CALLER holds. This module reads none of them: it carries nothing
132 * between calls, so there is no state to keep and nothing to wipe. The parameter is there so
133 * a caller drives every namespace the same way.
134 */
135typedef struct
136{
137 ProfibusFcsArgs fcs_args;
138 ProfibusBuildSd1Args build_sd1_args;
139 ProfibusBuildSd2Args build_sd2_args;
140 ProfibusBuildSd3Args build_sd3_args;
141 ProfibusParseArgs parse_args;
142 proto_bool ok;
143 uint8_t value;
144 size_t n;
145} ProfibusVars;
146
147/** @brief The operands and the outcome. */
148extern ProfibusVars ProfibusV;
149
150/** @brief The entries. */
151typedef struct
152{
153 void (*const fcs)(uint8_t *work);
154 void (*const build_sd1)(uint8_t *work);
155 void (*const build_sd2)(uint8_t *work);
156 void (*const build_sd3)(uint8_t *work);
157 void (*const parse)(uint8_t *work);
158} ProfibusNs;
159
160// What the table binds, defined once in the .c and taking one parameter each: everything
161// else an entry needs is an operand in ProfibusV or a region of the borrow at a fixed offset.
162void protocore_profibus_fcs(uint8_t *work);
163void protocore_profibus_build_sd1(uint8_t *work);
164void protocore_profibus_build_sd2(uint8_t *work);
165void protocore_profibus_build_sd3(uint8_t *work);
166void protocore_profibus_parse(uint8_t *work);
167
168// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
169// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
170// `Profibus.fcs(work)` resolves to a named function and becomes a DIRECT call. An extern table
171// leaves the call indirect and the symbol live at every level, -O2 -flto included.
172static const ProfibusNs Profibus __attribute__((unused)) = {
173 .fcs = protocore_profibus_fcs,
174 .build_sd1 = protocore_profibus_build_sd1,
175 .build_sd2 = protocore_profibus_build_sd2,
176 .build_sd3 = protocore_profibus_build_sd3,
177 .parse = protocore_profibus_parse,
178};
179
181
182#endif // PROTOCORE_ENABLE_PROFIBUS
183
184#endif // PROTOCORE_PROFIBUS_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