ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
melsec.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 melsec.h
6 * @brief Mitsubishi MELSEC MC protocol (binary 3E frame) codec (PROTOCORE_ENABLE_MELSEC) -
7 * zero-heap batch-read request builder + response parser for MELSEC PLCs over TCP/UDP.
8 *
9 * The QnA-compatible binary 3E request frame (all multi-octet fields LITTLE-endian, unlike
10 * the big-endian PLC protocols):
11 * @code
12 * 50 00 subheader (request)
13 * 00 network number
14 * FF PC (station) number
15 * FF 03 request destination module I/O number (0x03FF)
16 * 00 request destination multidrop station
17 * LL LL request data length (the octets from the monitoring timer on)
18 * TT TT monitoring timer
19 * 01 04 command (0x0401 batch read)
20 * 00 00 subcommand (0x0000 word units)
21 * dd dd dd head device number (3 octets)
22 * CC device code (D = 0xA8, M = 0x90, ...)
23 * pp pp number of device points
24 * @endcode
25 * The response subheader is `D0 00`, followed by the same routing, a 2-octet length, a
26 * 2-octet end code (0x0000 = success), then the read data (2 octets per word point).
27 *
28 * Frame layout + device codes verified against a third-party MC-protocol implementation.
29 * This is the codec; the TCP/UDP send is the application's.
30 *
31 * @author Douglas Quigg (dstroy0)
32 * @date 2026
33 */
34
35#ifndef PROTOCORE_MELSEC_H
36#define PROTOCORE_MELSEC_H
37
38#include "protocore_config.h" // the entry point: protocore_types.h for the widths
39
40#if PROTOCORE_ENABLE_MELSEC
41
43
44// This module holds nothing between calls, so it carves no borrow and states none. An entry
45// takes one all the same, and never reads it, so every namespace in the tree is invoked the
46// same way.
47
48#define MELSEC_3E_REQ_SUBHEADER0 0x50 ///< request subheader (sent 0x50 then 0x00)
49#define MELSEC_3E_REQ_SUBHEADER1 0x00
50#define MELSEC_3E_RES_SUBHEADER0 0xD0 ///< response subheader (0xD0 0x00)
51#define MELSEC_3E_RES_SUBHEADER1 0x00
52
53#define MELSEC_NETWORK_DEFAULT 0x00 ///< local network
54#define MELSEC_PROTOCORE_DEFAULT 0xFF ///< host station
55#define MELSEC_DEST_IO_DEFAULT 0x03FF ///< own station CPU
56#define MELSEC_DEST_MULTIDROP_DEFAULT 0x00
57
58#define MELSEC_CMD_BATCH_READ 0x0401 ///< batch read
59#define MELSEC_CMD_BATCH_WRITE 0x1401 ///< batch write
60#define MELSEC_SUBCMD_WORD 0x0000 ///< word units
61#define MELSEC_SUBCMD_BIT 0x0001 ///< bit units
62
63// Device (area) codes for the binary 3E frame.
64#define MELSEC_DEV_D 0xA8 ///< data register (D)
65#define MELSEC_DEV_R 0xAF ///< file/extension register (R)
66#define MELSEC_DEV_M 0x90 ///< auxiliary relay (M)
67#define MELSEC_DEV_S 0x98 ///< state (S)
68#define MELSEC_DEV_X 0x9C ///< input (X)
69#define MELSEC_DEV_Y 0x9D ///< output (Y)
70#define MELSEC_DEV_TN 0xC2 ///< timer current value (TN)
71#define MELSEC_DEV_TS 0xC1 ///< timer contact (TS)
72#define MELSEC_DEV_CN 0xC5 ///< counter current value (CN)
73#define MELSEC_DEV_CS 0xC4 ///< counter contact (CS)
74
75#define MELSEC_ENDCODE_OK 0x0000 ///< response end code: success
76
77// Binary 3E frame geometry (octet offsets and fixed lengths).
78#define MELSEC_3E_READ_REQ_LEN 21 ///< total octets in a batch-read request frame
79#define MELSEC_3E_READ_REQ_DATA_LEN 12 ///< batch-read request data-length field: timer..points
80#define MELSEC_3E_RES_MIN_LEN 11 ///< shortest valid response: subheader through end code
81#define MELSEC_3E_RES_LEN_OFFSET 7 ///< offset of the response data-length field
82#define MELSEC_3E_RES_DATALEN_BASE 9 ///< offset where the data-length-counted region (end code) begins
83#define MELSEC_3E_RES_DATA_OFFSET 11 ///< offset of the response read data (after the end code)
84#define MELSEC_ENDCODE_LEN 2 ///< end-code octets (counted inside the data length)
85
86/** @brief A parsed 3E response. @ref data points INTO the source buffer (LE word values). */
87typedef struct
88{
89 uint16_t end_code; ///< 0x0000 on success
90 const uint8_t *data; ///< response payload (empty on error)
91 size_t data_len;
92} MelsecResponse;
93
94/** @brief What build_read takes: buf, cap, device_code, head_device, ... */
95typedef struct
96{
97 uint8_t *buf;
98 size_t cap;
99 uint8_t device_code; ///< MELSEC_DEV_* (or a raw device code)
100 uint32_t head_device; ///< starting device number (24-bit)
101 uint16_t points; ///< number of word points to read
102 uint16_t monitoring_timer; ///< the CPU monitoring timer (units of 250 ms; 0 = wait indefinitely)
103} MelsecBuildReadArgs;
104
105/** @brief What build_write takes: buf, cap, device_code, head_device, ... */
106typedef struct
107{
108 uint8_t *buf;
109 size_t cap;
110 uint8_t device_code;
111 uint32_t head_device;
112 uint16_t points;
113 uint16_t monitoring_timer;
114 const uint8_t *data;
115 size_t data_len;
116} MelsecBuildWriteArgs;
117
118/** @brief What parse_response takes: buf, len, out. */
119typedef struct
120{
121 const uint8_t *buf;
122 size_t len;
123 MelsecResponse *out;
124} MelsecParseResponseArgs;
125
126/**
127 * @brief Mitsubishi MELSEC MC protocol (binary 3E frame) codec (PROTOCORE_ENABLE_MELSEC) - zero-heap batch-read request
128 * builder + response parser for MELSEC PLCs over TCP/UDP.
129 *
130 * A caller sets the members a call takes, invokes it through ::Melsec with the bytes it runs
131 * out of, and reads the outcome off the same handle.
132 *
133 * Melsec.build_read_args.buf = ...;
134 * Melsec.build_read_args.cap = ...;
135 * Melsec.build_read_args.device_code = ...;
136 * Melsec.build_read_args.head_device = ...;
137 * Melsec.build_read_args.points = ...;
138 * Melsec.build_read_args.monitoring_timer = ...;
139 * Melsec.build_read(work);
140 * // Melsec.n is what the call reports
141 *
142 * @var MelsecNs::build_read_args what build_read takes: buf, cap, device_code, head_device,
143 * @var MelsecNs::build_write_args what build_write takes: buf, cap, device_code, head_device,
144 * @var MelsecNs::parse_response_args what parse_response takes: buf, len, out
145 * @var MelsecNs::ok a call's true/false outcome
146 * @var MelsecNs::n total octets written (21), or 0 on overflow / bad input
147 * @var MelsecNs::build_read build a binary 3E batch-read (word units) request
148 * @var MelsecNs::build_write build a binary 3E batch-write (word units) request: the same device ...
149 * @var MelsecNs::parse_response parse + validate a binary 3E response (subheader 0xD0 0x00, length, ...
150 *
151 * @c work is bytes the CALLER holds. This module reads none of them: it carries nothing
152 * between calls, so there is no state to keep and nothing to wipe. The parameter is there so
153 * a caller drives every namespace the same way.
154 */
155typedef struct
156{
157 MelsecBuildReadArgs build_read_args;
158 MelsecBuildWriteArgs build_write_args;
159 MelsecParseResponseArgs parse_response_args;
160 proto_bool ok;
161 size_t n;
162} MelsecVars;
163
164/** @brief The operands and the outcome. */
165extern MelsecVars MelsecV;
166
167/** @brief The entries. */
168typedef struct
169{
170 void (*const build_read)(uint8_t *work);
171 void (*const build_write)(uint8_t *work);
172 void (*const parse_response)(uint8_t *work);
173} MelsecNs;
174
175// What the table binds, defined once in the .c and taking one parameter each: everything
176// else an entry needs is an operand in MelsecV or a region of the borrow at a fixed offset.
177void protocore_melsec_build_read(uint8_t *work);
178void protocore_melsec_build_write(uint8_t *work);
179void protocore_melsec_parse_response(uint8_t *work);
180
181// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
182// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
183// `Melsec.build_read(work)` resolves to a named function and becomes a DIRECT call. An extern table
184// leaves the call indirect and the symbol live at every level, -O2 -flto included.
185static const MelsecNs Melsec __attribute__((unused)) = {
186 .build_read = protocore_melsec_build_read,
187 .build_write = protocore_melsec_build_write,
188 .parse_response = protocore_melsec_parse_response,
189};
190
192
193#endif // PROTOCORE_ENABLE_MELSEC
194
195#endif // PROTOCORE_MELSEC_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