ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
mbplus.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 mbplus.h
6 * @brief Modbus Plus HDLC token-bus frame codec (PROTOCORE_ENABLE_MBPLUS).
7 *
8 * Modbus Plus is Schneider's 1 Mbit/s token-passing peer bus. Its data link is HDLC-framed: a frame is
9 * delimited by the HDLC flag 0x7E, carries an address / control / the LLC+Modbus routing path + data,
10 * and ends with a CRC-16 (CRC-16/X-25). This codec builds/validates the HDLC frame (with 0x7E-flag
11 * delimiting and the standard bit/byte transparency handled at the byte level) around a Modbus routing
12 * path + PDU, plus the token-rotation helper that computes the next station in the logical ring:
13 *
14 * [7E][address][control][routing path...][data...][CRC-16 lo][CRC-16 hi][7E]
15 *
16 * The physical 1 Mbit/s bus is hardware-gated; this is the frame + token-MAC layer, reusing the shipped
17 * Modbus PDU model for the data. Pure, zero heap, no stdlib, host-testable.
18 */
19
20#ifndef PROTOCORE_MBPLUS_H
21#define PROTOCORE_MBPLUS_H
22
23#include "protocore_config.h" // the entry point: protocore_types.h for the widths
24
25#if PROTOCORE_ENABLE_MBPLUS
26
28
29// This module holds nothing between calls, so it carves no borrow and states none. An entry
30// takes one all the same, and never reads it, so every namespace in the tree is invoked the
31// same way.
32
33// Modbus Plus HDLC wire constants: integer values compared/emitted, in a namespacing struct.
34#define MBPLUS_FLAG 0x7E ///< HDLC frame delimiter.
35#define MBPLUS_MAX_STATION 64 ///< stations 1..64 on a Modbus Plus segment.
36#define MBPLUS_CTRL_DATA 0x00 ///< data frame control.
37#define MBPLUS_CTRL_TOKEN 0x01 ///< token pass control.
38
39/** @brief A parsed Modbus Plus frame (payload points into the input). */
40typedef struct
41{
42 uint8_t address;
43 uint8_t control;
44 const uint8_t *payload;
45 size_t payload_len;
46} MbPlusFrame;
47
48/** @brief What crc takes: bytes, len. */
49typedef struct
50{
51 const uint8_t *bytes;
52 size_t len;
53} MbplusCrcArgs;
54
55/** @brief What build takes: address, control, payload, payload_len, ... */
56typedef struct
57{
58 uint8_t address; ///< the destination station (1..64)
59 uint8_t control; ///< MBPLUS_CTRL_DATA / MBPLUS_CTRL_TOKEN
60 const uint8_t *payload; ///< the routing path + Modbus PDU (may be null if payload_len == 0)
61 size_t payload_len; ///< its length
62 uint8_t *out;
63 size_t cap;
64} MbplusBuildArgs;
65
66/** @brief What parse takes: frame, len, out. */
67typedef struct
68{
69 const uint8_t *frame;
70 size_t len;
71 MbPlusFrame *out;
72} MbplusParseArgs;
73
74/** @brief What next_token takes: current, max_station. */
75typedef struct
76{
77 uint8_t current; ///< this station's address (1..max_station)
78 uint8_t max_station; ///< the highest active station on the segment
79} MbplusNextTokenArgs;
80
81/**
82 * @brief Modbus Plus HDLC token-bus frame codec (PROTOCORE_ENABLE_MBPLUS).
83 *
84 * A caller sets the members a call takes, invokes it through ::Mbplus with the bytes it runs
85 * out of, and reads the outcome off the same handle.
86 *
87 * Mbplus.crc_args.bytes = ...;
88 * Mbplus.crc_args.len = ...;
89 * Mbplus.crc(work);
90 * // Mbplus.value is what the call reports
91 *
92 * @var MbplusNs::crc_args what crc takes: bytes, len
93 * @var MbplusNs::build_args what build takes: address, control, payload, payload_len,
94 * @var MbplusNs::parse_args what parse takes: frame, len, out
95 * @var MbplusNs::next_token_args what next_token takes: current, max_station
96 * @var MbplusNs::ok a call's true/false outcome
97 * @var MbplusNs::value the next station address, wrapping from max_station back to 1
98 * @var MbplusNs::n the frame length (1 + 1 + 1 + payload_len + 2 + 1), or 0 on ...
99 * @var MbplusNs::crc CRC-16/X-25 (the Modbus Plus HDLC FCS) over len bytes
100 * @var MbplusNs::build build a Modbus Plus HDLC frame: 7E addr ctrl [payload] CRClo CRChi ...
101 * @var MbplusNs::parse validate the flags + CRC and parse a Modbus Plus frame. true if ...
102 * @var MbplusNs::next_token compute the next token holder in the logical ring
103 *
104 * @c work is bytes the CALLER holds. This module reads none of them: it carries nothing
105 * between calls, so there is no state to keep and nothing to wipe. The parameter is there so
106 * a caller drives every namespace the same way.
107 */
108typedef struct
109{
110 MbplusCrcArgs crc_args;
111 MbplusBuildArgs build_args;
112 MbplusParseArgs parse_args;
113 MbplusNextTokenArgs next_token_args;
114 proto_bool ok;
115 uint16_t value;
116 size_t n;
117} MbplusVars;
118
119/** @brief The operands and the outcome. */
120extern MbplusVars MbplusV;
121
122/** @brief The entries. */
123typedef struct
124{
125 void (*const crc)(uint8_t *work);
126 void (*const build)(uint8_t *work);
127 void (*const parse)(uint8_t *work);
128 void (*const next_token)(uint8_t *work);
129} MbplusNs;
130
131// What the table binds, defined once in the .c and taking one parameter each: everything
132// else an entry needs is an operand in MbplusV or a region of the borrow at a fixed offset.
133void protocore_mbplus_crc(uint8_t *work);
134void protocore_mbplus_build(uint8_t *work);
135void protocore_mbplus_parse(uint8_t *work);
136void protocore_mbplus_next_token(uint8_t *work);
137
138// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
139// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
140// `Mbplus.crc(work)` resolves to a named function and becomes a DIRECT call. An extern table
141// leaves the call indirect and the symbol live at every level, -O2 -flto included.
142static const MbplusNs Mbplus __attribute__((unused)) = {
143 .crc = protocore_mbplus_crc,
144 .build = protocore_mbplus_build,
145 .parse = protocore_mbplus_parse,
146 .next_token = protocore_mbplus_next_token,
147};
148
150
151#endif // PROTOCORE_ENABLE_MBPLUS
152
153#endif // PROTOCORE_MBPLUS_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