ProtoCore v0.0.1
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
simatic.h
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file simatic.h
6 * @brief Siemens SIMATIC serial point-to-point link (PC_ENABLE_SIMATIC) - the 3964R link protocol +
7 * the RK512 computer-link telegrams, zero-heap.
8 *
9 * The pre-Ethernet Siemens point-to-point link, two layers:
10 *
11 * - **3964R** - a byte-oriented, half-duplex link protocol (S5/S7 PtP CP modules CP 341 / CP 441 /
12 * CP 524 / CP 525). A block is framed @code STX <data, DLE bytes doubled> DLE ETX [BCC] @endcode
13 * with an interactive per-block handshake: the sender emits STX and waits for the receiver's DLE
14 * (ready) before sending the block, then waits for a final DLE (ok) or NAK (retry). On a simultaneous
15 * STX collision the low-priority station yields. The "R" variant appends a BCC - the longitudinal XOR
16 * (even parity) of every character of the block after STX, i.e. the stuffed data and the terminating
17 * DLE ETX. A payload byte equal to DLE (0x10) is doubled (transparency); a doubled DLE contributes
18 * 0x10 ^ 0x10 = 0 to the XOR, so it does not change the BCC.
19 *
20 * - **RK512** - fixed-header request/reaction telegrams carried as the 3964R block payload: SEND (write
21 * words to the partner) and FETCH (read words from the partner), addressing a data block / flag / I-O
22 * area by number + word offset + count. Siemens words are big-endian.
23 *
24 * Pure, host-tested (native_simatic) against an independent python 3964R+RK512 reference peer; the
25 * RS-232 / RS-485 UART is the application's (like the other serial-bus codecs). Control-char handshake,
26 * QVZ/ZVZ timeouts, priority arbitration, BCC, and the RK512 header layout follow the Siemens "3964(R)
27 * transmission protocol" and "RK 512 computer link" CP-module manuals.
28 *
29 * @author Douglas Quigg (dstroy0)
30 * @date 2026
31 */
32
33#ifndef PROTOCORE_SIMATIC_H
34#define PROTOCORE_SIMATIC_H
35
36#include "protocore_config.h"
37
38#if PC_ENABLE_SIMATIC
39
40#include <stddef.h>
41#include <stdint.h>
42
43// 3964R control characters (wire bytes).
44#define SIMATIC_STX 0x02
45#define SIMATIC_DLE 0x10
46#define SIMATIC_ETX 0x03
47#define SIMATIC_NAK 0x15
48
49// ---------------------------------------------------------------------------
50// 3964R block framing (pure builders/parsers - the state machine owns STX + the handshake)
51// ---------------------------------------------------------------------------
52
53/**
54 * @brief 3964R BCC: the longitudinal XOR (even parity) over @p len bytes at @p data.
55 *
56 * The block-check is taken over every transmitted character after STX - the DLE-stuffed payload plus the
57 * terminating DLE ETX - so callers pass exactly those bytes. XOR (not DF1's 2's-complement sum): a doubled
58 * DLE pair cancels (0x10 ^ 0x10 = 0).
59 */
60uint8_t pc_3964r_bcc(const uint8_t *data, size_t len);
61
62/**
63 * @brief Build the 3964R block body: DLE-stuffed @p data, then DLE ETX, then (if @p with_bcc) the BCC.
64 *
65 * The STX and the connect handshake are emitted by the link state machine; this builds only the block body
66 * a caller hands the state machine (or a test drives directly).
67 * @return octets written, or 0 on overflow / bad input.
68 */
69size_t pc_3964r_build_block(uint8_t *buf, size_t cap, const uint8_t *data, size_t len, bool with_bcc);
70
71/**
72 * @brief Parse + validate a 3964R block body (the bytes after STX): un-stuff the payload, check DLE ETX
73 * and the BCC (when @p with_bcc).
74 * @param out receives the de-stuffed payload.
75 * @param out_cap capacity of @p out.
76 * @param out_len receives the payload length.
77 * @return true on a complete, check-valid block; false on bad framing, a lone control byte, truncation,
78 * an out overflow, or a BCC mismatch (fail-closed).
79 */
80bool pc_3964r_parse_block(const uint8_t *buf, size_t len, bool with_bcc, uint8_t *out, size_t out_cap, size_t *out_len);
81
82// ---------------------------------------------------------------------------
83// 3964R link state machine (interactive half-duplex: STX/DLE handshake, retries, priority arbitration)
84// ---------------------------------------------------------------------------
85
86/** @brief Link state (one job in flight; half-duplex). */
87enum class Simatic3964State : uint8_t
88{
89 IDLE, ///< nothing in flight
90 TX_AWAIT_CONN, ///< sent STX, awaiting the partner's connect DLE (QVZ)
91 TX_AWAIT_END, ///< sent the block, awaiting the partner's end DLE / NAK (QVZ)
92 RX_COLLECT ///< replied DLE to a partner STX, collecting the block (ZVZ per-char)
93};
94
95/** @brief Sink for one outbound byte (the state machine writes to the UART through this). */
96typedef void (*Simatic3964TxFn)(void *user, uint8_t byte);
97/** @brief Delivery of a fully received, check-valid block payload. */
98typedef void (*Simatic3964RxFn)(void *user, const uint8_t *data, size_t len);
99
100/**
101 * @brief 3964R link owner - all link state in one named context (no file-scope mutable). The tx/rx buffers
102 * are fixed BSS; @p user is threaded to the callbacks.
103 */
104struct Simatic3964Ctx
105{
106 Simatic3964State state;
107 bool high_priority; ///< the priority bit; on an STX collision the low-priority side yields to receive
108 bool with_bcc; ///< the "R" (BCC) variant
109 Simatic3964TxFn tx; ///< outbound-byte sink
110 Simatic3964RxFn rx; ///< received-block delivery
111 void *user; ///< passed to tx / rx
112
113 uint8_t txbuf[PC_SIMATIC_BLOCK_MAX]; ///< the block body being sent (built once, re-sent on retry)
114 size_t txlen;
115 uint8_t rxbuf[PC_SIMATIC_BLOCK_MAX]; ///< raw inbound block body (pre un-stuffing)
116 size_t rxpos;
117
118 uint8_t block_retries; ///< block resends this connection (max 6)
119 uint8_t conn_retries; ///< connection reattempts (max 6)
120 uint32_t deadline_ms; ///< QVZ (handshake) / ZVZ (inter-char) expiry
121 bool prev_dle; ///< rx terminator scan: previous rx byte was an un-paired DLE
122 bool await_bcc; ///< rx: DLE ETX seen, the next byte is the BCC (R variant)
123};
124
125/** @brief 3964R retry / timeout limits (Siemens defaults). */
126#define SIMATIC_MAX_BLOCK_RETRY 6
127#define SIMATIC_MAX_CONN_RETRY 6
128
129/** @brief Initialize the link. @p high_priority: one end true, the other false (collision arbitration). */
130void pc_3964r_init(Simatic3964Ctx *ctx, bool high_priority, bool with_bcc, Simatic3964TxFn tx, Simatic3964RxFn rx,
131 void *user);
132
133/**
134 * @brief Start sending @p data (one job in flight). Emits STX and arms the connect timeout.
135 * @return true if accepted; false if a job is already in flight or @p len exceeds the block buffer.
136 */
137bool pc_3964r_send(Simatic3964Ctx *ctx, const uint8_t *data, size_t len, uint32_t now_ms);
138
139/** @brief Feed one inbound byte at @p now_ms; drives the handshake / block collection. */
140void pc_3964r_rx_byte(Simatic3964Ctx *ctx, uint8_t b, uint32_t now_ms);
141
142/** @brief Drive timeouts (QVZ/ZVZ) + retries; call periodically with the current time. */
143void pc_3964r_tick(Simatic3964Ctx *ctx, uint32_t now_ms);
144
145/** @brief True when no job is in flight and no block is being received. */
146bool pc_3964r_idle(const Simatic3964Ctx *ctx);
147
148// ---------------------------------------------------------------------------
149// RK512 computer-link telegrams (carried as the 3964R block payload; big-endian words)
150// ---------------------------------------------------------------------------
151
152/** @brief RK512 job / telegram identifier (the "Kennung" command byte). */
153enum class Rk512Cmd : uint8_t
154{
155 SEND = 0x00, ///< write words to the partner
156 FETCH = 0x01, ///< read words from the partner
157 REACTION = 0x02 ///< the partner's reaction (acknowledge) telegram
158};
159
160/** @brief RK512 memory area code (the operand area a job addresses). */
161enum class Rk512Area : uint8_t
162{
163 DB = 0x01, ///< data block (DBNR selects which)
164 DX = 0x02, ///< extended data block
165 MB = 0x03, ///< flag / marker (M)
166 EB = 0x04, ///< process input image (E)
167 AB = 0x05, ///< process output image (A)
168 PB = 0x06, ///< peripheral / I-O
169 ZB = 0x07, ///< counter (Z)
170 TB = 0x08 ///< timer (T)
171};
172
173/** @brief A decoded RK512 header. */
174struct Rk512Header
175{
176 Rk512Cmd cmd;
177 Rk512Area area;
178 uint8_t dbnr; ///< data-block number (when area is DB/DX)
179 uint16_t addr; ///< start word offset (DBADR)
180 uint16_t count; ///< word count (ANZ)
181};
182
183/**
184 * @brief Build a SEND telegram header + the @p wcount big-endian data words at @p words.
185 * @return octets written, or 0 on overflow / bad input.
186 */
187size_t pc_rk512_build_send(uint8_t *buf, size_t cap, Rk512Area area, uint8_t dbnr, uint16_t addr, const uint16_t *words,
188 uint16_t wcount);
189
190/**
191 * @brief Build a FETCH telegram header (no data words - the partner returns them).
192 * @return octets written, or 0 on overflow / bad input.
193 */
194size_t pc_rk512_build_fetch(uint8_t *buf, size_t cap, Rk512Area area, uint8_t dbnr, uint16_t addr, uint16_t wcount);
195
196/**
197 * @brief Build a reaction (acknowledge) telegram carrying @p status (0 = ok).
198 * @return octets written, or 0 on overflow.
199 */
200size_t pc_rk512_build_reaction(uint8_t *buf, size_t cap, uint16_t status);
201
202/** @brief Parse an RK512 header off a telegram. @return true on a complete, valid header. */
203bool pc_rk512_parse_header(const uint8_t *buf, size_t len, Rk512Header *out);
204
205/**
206 * @brief Parse a reaction telegram: the status word, and (for a FETCH response) a pointer to the data
207 * words + their byte length inside @p buf.
208 * @return true on a valid reaction telegram.
209 */
210bool pc_rk512_parse_reaction(const uint8_t *buf, size_t len, uint16_t *status, const uint8_t **data, size_t *dlen);
211
212#endif // PC_ENABLE_SIMATIC
213
214#endif // PROTOCORE_SIMATIC_H
User-facing configuration for ProtoCore.
#define PC_SIMATIC_BLOCK_MAX
3964R block-body buffer size (built/received bytes: DLE-stuffed payload + DLE ETX + BCC).