ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
sb_modbus.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 sb_modbus.cpp
6 * @brief Modbus-master southbound driver adapter (see sb_modbus.h).
7 */
8
10
11#if PC_ENABLE_SOUTHBOUND && PC_ENABLE_MODBUS_MASTER
12
14
15namespace
16{
17// Read a contiguous span of `n` registers (1..125) at `first` in one Modbus request; write the parsed
18// values to `out` as int32. Shared by the single-point and block reads. Returns the register count
19// (>= 0), a negative transport error (propagated from txn), PC_SB_MODBUS_EXCEPTION on a Modbus
20// exception reply, or Sb::SB_ERR_ARG on a bad argument / malformed reply.
21int sb_modbus_read_span(pc_sb_modbus_ctx *c, uint32_t first, int32_t *out, size_t n)
22{
23 // A Modbus register address is 16-bit and a single request reads at most 125 registers.
24 if (n == 0 || n > 125 || first > 0xFFFFu || first + n > 0x10000u)
25 {
26 return Sb::SB_ERR_ARG;
27 }
28
29 uint8_t req[12];
30 size_t rn =
31 pc_modbus_build_read((uint8_t)c->fc, c->txid++, c->unit, (uint16_t)first, (uint16_t)n, req, sizeof(req));
32 if (rn == 0)
33 {
34 return Sb::SB_ERR_ARG;
35 }
36
37 uint8_t resp[MODBUS_ADU_MAX];
38 int pn = c->txn(c->io, req, rn, resp, sizeof(resp));
39 if (pn < 0)
40 {
41 return pn; // transport error, propagated unchanged
42 }
43
44 uint16_t regs[125];
45 uint8_t ex = 0;
46 int got = pc_modbus_parse_response(resp, (size_t)pn, regs, n, &ex);
47 if (got < 0)
48 {
49 return Sb::SB_ERR_ARG; // malformed / short frame
50 }
51 c->last_exception = ex;
52 if (ex)
53 {
54 return PC_SB_MODBUS_EXCEPTION;
55 }
56 for (int i = 0; i < got; i++)
57 {
58 out[i] = (int32_t)regs[i];
59 }
60 return got;
61}
62
63int sb_modbus_read(void *vctx, uint32_t point, int32_t *value_out)
64{
65 pc_sb_modbus_ctx *c = (pc_sb_modbus_ctx *)vctx;
66 int got = sb_modbus_read_span(c, point, value_out, 1);
67 if (got < 0)
68 {
69 return got;
70 }
71 return (got == 1) ? Sb::SB_OK : Sb::SB_ERR_ARG; // a valid reply always carries the one register
72}
73
74int sb_modbus_read_block(void *vctx, uint32_t first, int32_t *out, size_t n)
75{
76 return sb_modbus_read_span((pc_sb_modbus_ctx *)vctx, first, out, n);
77}
78
79// Run one write request through the transport seam and interpret the reply. Shared by the single-point
80// and block writes: `req`/`req_len` is the built request. Returns the register count written (>= 0), a
81// propagated transport error, PC_SB_MODBUS_EXCEPTION on a Modbus exception reply, or Sb::SB_ERR_ARG on
82// a malformed reply.
83int sb_modbus_write_txn(pc_sb_modbus_ctx *c, const uint8_t *req, size_t req_len)
84{
85 uint8_t resp[MODBUS_ADU_MAX];
86 int pn = c->txn(c->io, req, req_len, resp, sizeof(resp));
87 if (pn < 0)
88 {
89 return pn; // transport error, propagated unchanged
90 }
91 uint8_t ex = 0;
92 int w = pc_modbus_parse_write_response(resp, (size_t)pn, nullptr, &ex);
93 if (w < 0)
94 {
95 return Sb::SB_ERR_ARG; // malformed / short frame
96 }
97 c->last_exception = ex;
98 if (ex)
99 {
100 return PC_SB_MODBUS_EXCEPTION;
101 }
102 return w;
103}
104
105int sb_modbus_write(void *vctx, uint32_t point, int32_t value)
106{
107 pc_sb_modbus_ctx *c = (pc_sb_modbus_ctx *)vctx;
108 if (point > 0xFFFFu || value < 0 || value > 0xFFFF) // a Modbus register is a 16-bit address / value
109 {
110 return Sb::SB_ERR_ARG;
111 }
112 uint8_t req[12];
113 size_t rn = pc_modbus_build_write_single(c->txid++, c->unit, (uint16_t)point, (uint16_t)value, req, sizeof(req));
114 if (rn == 0)
115 {
116 return Sb::SB_ERR_ARG;
117 }
118 int w = sb_modbus_write_txn(c, req, rn);
119 if (w < 0)
120 {
121 return w;
122 }
123 return (w == 1) ? Sb::SB_OK : Sb::SB_ERR_ARG; // a valid reply echoes the one register
124}
125
126int sb_modbus_write_block(void *vctx, uint32_t first, const int32_t *in, size_t n)
127{
128 pc_sb_modbus_ctx *c = (pc_sb_modbus_ctx *)vctx;
129 // FC 0x10 writes at most 123 registers per request; the span must stay in the 16-bit address space.
130 if (n == 0 || n > 123 || first > 0xFFFFu || first + n > 0x10000u)
131 {
132 return Sb::SB_ERR_ARG;
133 }
134 uint16_t vals[123];
135 for (size_t i = 0; i < n; i++)
136 {
137 if (in[i] < 0 || in[i] > 0xFFFF)
138 {
139 return Sb::SB_ERR_ARG;
140 }
141 vals[i] = (uint16_t)in[i];
142 }
143 uint8_t req[13 + 2 * 123];
144 size_t rn =
145 pc_modbus_build_write_multiple(c->txid++, c->unit, (uint16_t)first, vals, (uint16_t)n, req, sizeof(req));
146 if (rn == 0)
147 {
148 return Sb::SB_ERR_ARG;
149 }
150 return sb_modbus_write_txn(c, req, rn); // count written (>= 0) / negative code
151}
152} // namespace
153
154int pc_sb_modbus_init(pc_sb_modbus_ctx *ctx, pc_sb_modbus_txn txn, void *io, ModbusFunction fc, uint8_t unit)
155{
156 if (!ctx || !txn)
157 {
158 return Sb::SB_ERR_ARG;
159 }
160 if (fc != ModbusFunction::MODBUS_FC_READ_HOLDING_REGS && fc != ModbusFunction::MODBUS_FC_READ_INPUT_REGS)
161 {
162 return Sb::SB_ERR_ARG;
163 }
164 ctx->txn = txn;
165 ctx->io = io;
166 ctx->fc = fc;
167 ctx->unit = unit;
168 ctx->txid = 0;
169 ctx->last_exception = 0;
170 return Sb::SB_OK;
171}
172
173int pc_sb_modbus_driver(SouthboundDriver *drv_out, const char *name, pc_sb_modbus_ctx *ctx)
174{
175 if (!drv_out || !name || !ctx || !ctx->txn)
176 {
177 return Sb::SB_ERR_ARG;
178 }
179 // Holding registers are read/write; input registers are read-only (a Modbus input register cannot be
180 // written), so an input-register driver leaves write / write_block unbound (framework: SB_ERR_UNSUPPORTED).
181 bool writable = (ctx->fc == ModbusFunction::MODBUS_FC_READ_HOLDING_REGS);
182 drv_out->name = name;
183 drv_out->read = &sb_modbus_read;
184 drv_out->write = writable ? &sb_modbus_write : nullptr;
185 drv_out->read_block = &sb_modbus_read_block;
186 drv_out->write_block = writable ? &sb_modbus_write_block : nullptr;
187 drv_out->ctx = ctx;
188 return Sb::SB_OK;
189}
190
191#endif // PC_ENABLE_SOUTHBOUND && PC_ENABLE_MODBUS_MASTER
Modbus-master southbound driver adapter (PC_ENABLE_SOUTHBOUND && PC_ENABLE_MODBUS_MASTER).