ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
cclink.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 cclink.h
6 * @brief CC-Link (CLPA) cyclic fieldbus frame codec (PROTOCORE_ENABLE_CCLINK).
7 *
8 * CC-Link is Mitsubishi's (CLPA) factory fieldbus. The classic CC-Link master polls remote stations
9 * over RS-485 exchanging a cyclic process image split into bit devices (RX/RY - remote input/output
10 * bits) and word devices (RWr/RWw - remote registers). This codec builds/validates the cyclic frame a
11 * master/station exchanges:
12 *
13 * [station][command][RX/RY bit data...][RWr/RWw word data...][sum-checksum]
14 *
15 * A station's process image is a fixed BSS block; this frames it. The checksum is the low byte of the
16 * arithmetic sum of the framed bytes. The RS-485 timing and the CC-Link IE Field (Gigabit) PHY are the
17 * hardware-gated part; this is the frame + process-image accessors. Pure, zero heap, host-testable.
18 */
19
20#ifndef PROTOCORE_CCLINK_H
21#define PROTOCORE_CCLINK_H
22
23#include "protocore_config.h" // the entry point: protocore_types.h for the widths
24
25#if PROTOCORE_ENABLE_CCLINK
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// CC-Link command bytes: wire values compared/emitted, so integer constants in a namespacing struct.
34#define CCLINK_CMD_REFRESH 0x01 ///< cyclic refresh (master <-> station process image).
35#define CCLINK_CMD_POLL 0x02 ///< poll a station.
36#define CCLINK_CMD_TEST 0x0F ///< line test.
37
38/** @brief A parsed CC-Link frame (payload points into the input; caller knows the bit/word split). */
39typedef struct
40{
41 uint8_t station;
42 uint8_t command;
43 const uint8_t *payload; ///< the bit+word data region.
44 size_t payload_len;
45} CcLinkFrame;
46
47/** @brief What sum takes: bytes, len. */
48typedef struct
49{
50 const uint8_t *bytes;
51 size_t len;
52} CclinkSumArgs;
53
54/** @brief What build takes: station, command, bits, bit_len, words, ... */
55typedef struct
56{
57 uint8_t station; ///< station number 0..63
58 uint8_t command; ///< CCLINK_CMD_*
59 const uint8_t *bits; ///< the RX/RY bit-device bytes (may be null if bit_len == 0)
60 size_t bit_len; ///< number of bit-device bytes
61 const uint8_t *words; ///< the RWr/RWw word-device bytes (little-endian words; may be null if word_len == 0)
62 size_t word_len; ///< number of word-device bytes
63 uint8_t *out;
64 size_t cap;
65} CclinkBuildArgs;
66
67/** @brief What parse takes: frame, len, out. */
68typedef struct
69{
70 const uint8_t *frame;
71 size_t len;
72 CcLinkFrame *out;
73} CclinkParseArgs;
74
75/** @brief What get_bit takes: bits, bit_len, index. */
76typedef struct
77{
78 const uint8_t *bits;
79 size_t bit_len;
80 size_t index;
81} CclinkGetBitArgs;
82
83/** @brief What set_bit takes: bits, bit_len, index, value. */
84typedef struct
85{
86 uint8_t *bits;
87 size_t bit_len;
88 size_t index;
89 proto_bool value;
90} CclinkSetBitArgs;
91
92/** @brief What get_word takes: words, word_len, index. */
93typedef struct
94{
95 const uint8_t *words;
96 size_t word_len;
97 size_t index;
98} CclinkGetWordArgs;
99
100/**
101 * @brief CC-Link (CLPA) cyclic fieldbus frame codec (PROTOCORE_ENABLE_CCLINK).
102 *
103 * A caller sets the members a call takes, invokes it through ::Cclink with the bytes it runs
104 * out of, and reads the outcome off the same handle.
105 *
106 * Cclink.sum_args.bytes = ...;
107 * Cclink.sum_args.len = ...;
108 * Cclink.sum(work);
109 * // Cclink.value is what the call reports
110 *
111 * @var CclinkNs::sum_args what sum takes: bytes, len
112 * @var CclinkNs::build_args what build takes: station, command, bits, bit_len, words,
113 * @var CclinkNs::parse_args what parse takes: frame, len, out
114 * @var CclinkNs::get_bit_args what get_bit takes: bits, bit_len, index
115 * @var CclinkNs::set_bit_args what set_bit takes: bits, bit_len, index, value
116 * @var CclinkNs::get_word_args what get_word takes: words, word_len, index
117 * @var CclinkNs::ok a call's true/false outcome
118 * @var CclinkNs::value the value a call reports
119 * @var CclinkNs::n the frame length (2 + bit_len + word_len + 1), or 0 on overflow / ...
120 * @var CclinkNs::sum arithmetic-sum checksum: low byte of the sum of len bytes
121 * @var CclinkNs::build build a CC-Link cyclic frame: ...
122 * @var CclinkNs::parse validate the checksum and parse a CC-Link frame. true if the ...
123 * @var CclinkNs::get_bit read bit index (0-based) from a bit-device byte array
124 * @var CclinkNs::set_bit set/clear bit index in a bit-device byte array (no-op if out of ...
125 * @var CclinkNs::get_word read word index (0-based, little-endian) from a word-device byte ...
126 *
127 * @c work is bytes the CALLER holds. This module reads none of them: it carries nothing
128 * between calls, so there is no state to keep and nothing to wipe. The parameter is there so
129 * a caller drives every namespace the same way.
130 */
131typedef struct
132{
133 CclinkSumArgs sum_args;
134 CclinkBuildArgs build_args;
135 CclinkParseArgs parse_args;
136 CclinkGetBitArgs get_bit_args;
137 CclinkSetBitArgs set_bit_args;
138 CclinkGetWordArgs get_word_args;
139 proto_bool ok;
140 uint8_t value; ///< the checksum a sum reports
141 uint16_t u16; ///< the word a get_word reports: its own member, since value is an octet
142 size_t n;
143} CclinkVars;
144
145/** @brief The operands and the outcome. */
146extern CclinkVars CclinkV;
147
148/** @brief The entries. */
149typedef struct
150{
151 void (*const sum)(uint8_t *work);
152 void (*const build)(uint8_t *work);
153 void (*const parse)(uint8_t *work);
154 void (*const get_bit)(uint8_t *work);
155 void (*const set_bit)(uint8_t *work);
156 void (*const get_word)(uint8_t *work);
157} CclinkNs;
158
159// What the table binds, defined once in the .c and taking one parameter each: everything
160// else an entry needs is an operand in CclinkV or a region of the borrow at a fixed offset.
161void protocore_cclink_sum(uint8_t *work);
162void protocore_cclink_build(uint8_t *work);
163void protocore_cclink_parse(uint8_t *work);
164void protocore_cclink_get_bit(uint8_t *work);
165void protocore_cclink_set_bit(uint8_t *work);
166void protocore_cclink_get_word(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// `Cclink.sum(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 CclinkNs Cclink __attribute__((unused)) = {
173 .sum = protocore_cclink_sum,
174 .build = protocore_cclink_build,
175 .parse = protocore_cclink_parse,
176 .get_bit = protocore_cclink_get_bit,
177 .set_bit = protocore_cclink_set_bit,
178 .get_word = protocore_cclink_get_word,
179};
180
182
183#endif // PROTOCORE_ENABLE_CCLINK
184
185#endif // PROTOCORE_CCLINK_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