ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
mbplus.cpp
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 mbplus.cpp
6 * @brief Modbus Plus HDLC token-bus frame codec (see mbplus.h).
7 */
8
10#include "shared_primitives/crc.h" // PC_CRC16_X25
11
12#if PC_ENABLE_MBPLUS
13
14#include <string.h>
15
16uint16_t pc_mbplus_crc(const uint8_t *bytes, size_t len)
17{
18 // CRC-16/X-25: reflected poly 0x8408, init 0xFFFF, xorout 0xFFFF. test_crc diffs the shared
19 // engine against the loop that used to live here over every length 0..64.
20 return (uint16_t)pc_crc(&PC_CRC16_X25, bytes, len);
21}
22
23size_t pc_mbplus_build(uint8_t address, uint8_t control, const uint8_t *payload, size_t payload_len, uint8_t *out,
24 size_t cap)
25{
26 if (!out || (payload_len && !payload) || address < 1 || address > Mbplus::MBPLUS_MAX_STATION)
27 {
28 return 0;
29 }
30 size_t n = 1 + 1 + 1 + payload_len + 2 + 1; // 7E addr ctrl payload CRClo CRChi 7E
31 if (n > cap)
32 {
33 return 0;
34 }
35 size_t i = 0;
36 out[i++] = Mbplus::MBPLUS_FLAG;
37 size_t body = i;
38 out[i++] = address;
39 out[i++] = control;
40 if (payload_len)
41 {
42 memcpy(out + i, payload, payload_len);
43 i += payload_len;
44 }
45 uint16_t crc = pc_mbplus_crc(out + body, (i - body)); // over addr..last payload
46 out[i++] = (uint8_t)crc; // CRC low byte first
47 out[i++] = (uint8_t)(crc >> 8);
48 out[i++] = Mbplus::MBPLUS_FLAG;
49 return i;
50}
51
52bool pc_mbplus_parse(const uint8_t *frame, size_t len, MbPlusFrame *out)
53{
54 // Min: 7E addr ctrl CRClo CRChi 7E = 6 bytes.
55 if (!frame || !out || len < 6)
56 {
57 return false;
58 }
59 if (frame[0] != Mbplus::MBPLUS_FLAG || frame[len - 1] != Mbplus::MBPLUS_FLAG)
60 {
61 return false;
62 }
63 // Body is frame[1 .. len-2), the CRC is the last 2 body bytes.
64 size_t body_end = len - 1; // index of the trailing flag
65 size_t crc_pos = body_end - 2; // low byte of the CRC
66 size_t covered = crc_pos - 1; // number of bytes (addr..payload) the CRC covers
67 uint16_t want = pc_mbplus_crc(frame + 1, covered);
68 uint16_t got = (uint16_t)(frame[crc_pos] | (frame[crc_pos + 1] << 8));
69 if (want != got)
70 {
71 return false;
72 }
73 out->address = frame[1];
74 out->control = frame[2];
75 out->payload = (covered > 2) ? (frame + 3) : nullptr;
76 out->payload_len = covered - 2; // minus addr + ctrl
77 return true;
78}
79
80uint8_t pc_mbplus_next_token(uint8_t current, uint8_t max_station)
81{
82 if (max_station < 1)
83 {
84 return 1;
85 }
86 return (current >= max_station) ? 1 : (uint8_t)(current + 1);
87}
88
89#endif // PC_ENABLE_MBPLUS
Parameterized CRC engine - one source of truth for every cyclic redundancy check.
constexpr pc_crc_params PC_CRC16_X25
CRC-16/X-25 (HDLC FCS). check = 0x906E. Used by services/radio/thread, mbplus, nema_ts2.
Definition crc.h:190
uint32_t pc_crc(const pc_crc_params *p, const uint8_t *data, size_t len)
One-shot CRC of len octets at data.
Definition crc.h:158
Modbus Plus HDLC token-bus frame codec (PC_ENABLE_MBPLUS).