33 ModbusWriteCb write_cb =
nullptr;
35static ModbusCtx s_modbus;
37static bool bit_get(
const uint8_t *a, uint16_t i)
39 return (a[i >> 3] >> (i & 7)) & 1u;
41static void bit_set(uint8_t *a, uint16_t i,
bool v)
45 a[i >> 3] |= (uint8_t)(1u << (i & 7));
49 a[i >> 3] &= (uint8_t)~(1u << (i & 7));
53void pc_modbus_server_init()
55 memset(s_modbus.coils, 0,
sizeof(s_modbus.coils));
56 memset(s_modbus.discrete, 0,
sizeof(s_modbus.discrete));
57 memset(s_modbus.holding, 0,
sizeof(s_modbus.holding));
58 memset(s_modbus.input, 0,
sizeof(s_modbus.input));
59 s_modbus.write_cb =
nullptr;
62void pc_modbus_on_write(ModbusWriteCb cb)
64 s_modbus.write_cb = cb;
67bool pc_modbus_get_coil(uint16_t addr)
69 return (addr <
PC_MODBUS_COILS) ? bit_get(s_modbus.coils, addr) : false;
71void pc_modbus_set_coil(uint16_t addr,
bool on)
75 bit_set(s_modbus.coils, addr, on);
78bool pc_modbus_get_discrete_input(uint16_t addr)
82void pc_modbus_set_discrete_input(uint16_t addr,
bool on)
86 bit_set(s_modbus.discrete, addr, on);
89uint16_t pc_modbus_get_holding_reg(uint16_t addr)
93void pc_modbus_set_holding_reg(uint16_t addr, uint16_t value)
97 s_modbus.holding[addr] = value;
100uint16_t pc_modbus_get_input_reg(uint16_t addr)
104void pc_modbus_set_input_reg(uint16_t addr, uint16_t value)
108 s_modbus.input[addr] = value;
116static uint16_t rd16(
const uint8_t *p)
118 return (uint16_t)((p[0] << 8) | p[1]);
120static void wr16(uint8_t *p, uint16_t v)
122 p[0] = (uint8_t)(v >> 8);
123 p[1] = (uint8_t)(v & 0xFF);
127static size_t pdu_exception(ModbusFunction fc, ModbusException code, uint8_t *out)
129 out[0] = (uint8_t)((uint8_t)fc | 0x80);
130 out[1] = (uint8_t)code;
136static size_t pc_modbus_process_pdu(
const uint8_t *pdu,
size_t pdu_len, uint8_t *out,
size_t out_cap)
145 ModbusFunction fc =
static_cast<ModbusFunction
>(pdu[0]);
152 case ModbusFunction::MODBUS_FC_READ_COILS:
153 case ModbusFunction::MODBUS_FC_READ_DISCRETE_INPUTS: {
156 return pdu_exception(fc, ModbusException::MODBUS_EX_ILLEGAL_DATA_VALUE, out);
158 uint16_t start = rd16(pdu + 1), qty = rd16(pdu + 3);
160 const uint8_t *src = (fc == ModbusFunction::MODBUS_FC_READ_COILS) ? s_modbus.coils : s_modbus.discrete;
161 if (qty < 1 || qty > 2000)
163 return pdu_exception(fc, ModbusException::MODBUS_EX_ILLEGAL_DATA_VALUE, out);
165 if ((uint32_t)start + qty > limit)
167 return pdu_exception(fc, ModbusException::MODBUS_EX_ILLEGAL_DATA_ADDRESS, out);
169 uint8_t bytes = (uint8_t)((qty + 7) / 8);
170 if ((
size_t)2 + bytes > out_cap)
174 out[0] = (uint8_t)fc;
176 memset(out + 2, 0, bytes);
177 for (uint16_t i = 0; i < qty; i++)
179 if (bit_get(src, (uint16_t)(start + i)))
181 out[2 + (i >> 3)] |= (uint8_t)(1u << (i & 7));
184 return (
size_t)2 + bytes;
188 case ModbusFunction::MODBUS_FC_READ_HOLDING_REGS:
189 case ModbusFunction::MODBUS_FC_READ_INPUT_REGS: {
192 return pdu_exception(fc, ModbusException::MODBUS_EX_ILLEGAL_DATA_VALUE, out);
194 uint16_t start = rd16(pdu + 1), qty = rd16(pdu + 3);
197 const uint16_t *src = (fc == ModbusFunction::MODBUS_FC_READ_HOLDING_REGS) ? s_modbus.holding : s_modbus.input;
198 if (qty < 1 || qty > 125)
200 return pdu_exception(fc, ModbusException::MODBUS_EX_ILLEGAL_DATA_VALUE, out);
202 if ((uint32_t)start + qty > limit)
204 return pdu_exception(fc, ModbusException::MODBUS_EX_ILLEGAL_DATA_ADDRESS, out);
206 uint8_t bytes = (uint8_t)(qty * 2);
207 if ((
size_t)2 + bytes > out_cap)
211 out[0] = (uint8_t)fc;
213 for (uint16_t i = 0; i < qty; i++)
215 wr16(out + 2 + i * 2, src[start + i]);
217 return (
size_t)2 + bytes;
221 case ModbusFunction::MODBUS_FC_WRITE_SINGLE_COIL: {
224 return pdu_exception(fc, ModbusException::MODBUS_EX_ILLEGAL_DATA_VALUE, out);
226 uint16_t addr = rd16(pdu + 1), value = rd16(pdu + 3);
227 if (value != 0x0000 && value != 0xFF00)
229 return pdu_exception(fc, ModbusException::MODBUS_EX_ILLEGAL_DATA_VALUE, out);
233 return pdu_exception(fc, ModbusException::MODBUS_EX_ILLEGAL_DATA_ADDRESS, out);
235 bit_set(s_modbus.coils, addr, value == 0xFF00);
236 if (s_modbus.write_cb)
238 s_modbus.write_cb((uint8_t)fc, addr, 1);
249 case ModbusFunction::MODBUS_FC_WRITE_SINGLE_REG: {
252 return pdu_exception(fc, ModbusException::MODBUS_EX_ILLEGAL_DATA_VALUE, out);
254 uint16_t addr = rd16(pdu + 1), value = rd16(pdu + 3);
257 return pdu_exception(fc, ModbusException::MODBUS_EX_ILLEGAL_DATA_ADDRESS, out);
259 s_modbus.holding[addr] = value;
260 if (s_modbus.write_cb)
262 s_modbus.write_cb((uint8_t)fc, addr, 1);
273 case ModbusFunction::MODBUS_FC_WRITE_MULTIPLE_COILS: {
276 return pdu_exception(fc, ModbusException::MODBUS_EX_ILLEGAL_DATA_VALUE, out);
278 uint16_t start = rd16(pdu + 1), qty = rd16(pdu + 3);
280 if (qty < 1 || qty > 1968 || bc != (uint8_t)((qty + 7) / 8) || pdu_len < (
size_t)6 + bc)
282 return pdu_exception(fc, ModbusException::MODBUS_EX_ILLEGAL_DATA_VALUE, out);
286 return pdu_exception(fc, ModbusException::MODBUS_EX_ILLEGAL_DATA_ADDRESS, out);
288 for (uint16_t i = 0; i < qty; i++)
290 bool v = (pdu[6 + (i >> 3)] >> (i & 7)) & 1u;
291 bit_set(s_modbus.coils, (uint16_t)(start + i), v);
293 if (s_modbus.write_cb)
295 s_modbus.write_cb((uint8_t)fc, start, qty);
301 out[0] = (uint8_t)fc;
302 wr16(out + 1, start);
308 case ModbusFunction::MODBUS_FC_WRITE_MULTIPLE_REGS: {
311 return pdu_exception(fc, ModbusException::MODBUS_EX_ILLEGAL_DATA_VALUE, out);
313 uint16_t start = rd16(pdu + 1), qty = rd16(pdu + 3);
315 if (qty < 1 || qty > 123 || bc != (uint8_t)(qty * 2) || pdu_len < (
size_t)6 + bc)
317 return pdu_exception(fc, ModbusException::MODBUS_EX_ILLEGAL_DATA_VALUE, out);
321 return pdu_exception(fc, ModbusException::MODBUS_EX_ILLEGAL_DATA_ADDRESS, out);
323 for (uint16_t i = 0; i < qty; i++)
325 s_modbus.holding[start + i] = rd16(pdu + 6 + i * 2);
327 if (s_modbus.write_cb)
329 s_modbus.write_cb((uint8_t)fc, start, qty);
335 out[0] = (uint8_t)fc;
336 wr16(out + 1, start);
342 case ModbusFunction::MODBUS_FC_MASK_WRITE_REG: {
345 return pdu_exception(fc, ModbusException::MODBUS_EX_ILLEGAL_DATA_VALUE, out);
347 uint16_t addr = rd16(pdu + 1);
348 uint16_t and_mask = rd16(pdu + 3);
349 uint16_t or_mask = rd16(pdu + 5);
352 return pdu_exception(fc, ModbusException::MODBUS_EX_ILLEGAL_DATA_ADDRESS, out);
354 uint16_t cur = s_modbus.holding[addr];
355 s_modbus.holding[addr] = (uint16_t)((cur & and_mask) | (or_mask & (uint16_t)~and_mask));
356 if (s_modbus.write_cb)
358 s_modbus.write_cb((uint8_t)fc, addr, 1);
370 case ModbusFunction::MODBUS_FC_READ_WRITE_MULTIPLE_REGS: {
373 return pdu_exception(fc, ModbusException::MODBUS_EX_ILLEGAL_DATA_VALUE, out);
375 uint16_t r_start = rd16(pdu + 1);
376 uint16_t r_qty = rd16(pdu + 3);
377 uint16_t w_start = rd16(pdu + 5);
378 uint16_t w_qty = rd16(pdu + 7);
379 uint8_t w_bc = pdu[9];
380 if (r_qty < 1 || r_qty > 125 || w_qty < 1 || w_qty > 121 || w_bc != (uint8_t)(w_qty * 2) ||
381 pdu_len < (
size_t)10 + w_bc)
383 return pdu_exception(fc, ModbusException::MODBUS_EX_ILLEGAL_DATA_VALUE, out);
387 return pdu_exception(fc, ModbusException::MODBUS_EX_ILLEGAL_DATA_ADDRESS, out);
389 for (uint16_t i = 0; i < w_qty; i++)
391 s_modbus.holding[w_start + i] = rd16(pdu + 10 + i * 2);
393 if (s_modbus.write_cb)
395 s_modbus.write_cb((uint8_t)fc, w_start, w_qty);
397 uint8_t r_bytes = (uint8_t)(r_qty * 2);
398 if ((
size_t)2 + r_bytes > out_cap)
402 out[0] = (uint8_t)fc;
404 for (uint16_t i = 0; i < r_qty; i++)
406 wr16(out + 2 + i * 2, s_modbus.holding[r_start + i]);
408 return (
size_t)2 + r_bytes;
413 return pdu_exception(fc, ModbusException::MODBUS_EX_ILLEGAL_FUNCTION, out);
417size_t pc_modbus_process_adu(
const uint8_t *req,
size_t req_len, uint8_t *resp,
size_t pc_resp_cap)
419 if (req_len < 8 || pc_resp_cap < 8)
424 uint16_t tid = rd16(req);
425 uint16_t pid = rd16(req + 2);
426 uint16_t len = rd16(req + 4);
427 uint8_t uid = req[6];
433 if (len < 2 || (
size_t)6 + len > req_len)
438 const uint8_t *pdu = req + 7;
439 size_t pdu_len = (size_t)len - 1;
441 size_t rlen = pc_modbus_process_pdu(pdu, pdu_len, resp + 7, pc_resp_cap - 7);
449 wr16(resp + 4, (uint16_t)(1 + rlen));
454#if PC_ENABLE_MODBUS_RTU
457static uint16_t pc_modbus_crc16(
const uint8_t *data,
size_t len)
462size_t pc_modbus_rtu_process_adu(
const uint8_t *req,
size_t req_len, uint8_t *resp,
size_t pc_resp_cap, uint8_t my_addr)
464 if (req_len < 4 || pc_resp_cap < 4)
470 uint16_t want = pc_modbus_crc16(req, req_len - 2);
471 uint16_t got = (uint16_t)(req[req_len - 2] | (req[req_len - 1] << 8));
477 uint8_t addr = req[0];
478 bool broadcast = (addr == 0);
479 if (!broadcast && addr != my_addr)
484 const uint8_t *pdu = req + 1;
485 size_t pdu_len = req_len - 3;
487 size_t rlen = pc_modbus_process_pdu(pdu, pdu_len, resp + 1, pc_resp_cap - 3);
498 uint16_t crc = pc_modbus_crc16(resp, 1 + rlen);
499 resp[1 + rlen] = (uint8_t)(crc & 0xFFu);
500 resp[2 + rlen] = (uint8_t)(crc >> 8);
514static size_t ring_avail(
const TcpConn *c)
516 return pc_conn_available(c->
id);
518static void ring_peek(
const TcpConn *c,
size_t off, uint8_t *dst,
size_t n)
520 pc_conn_peek(c->
id, off, dst, n);
522static void ring_consume(
TcpConn *c,
size_t n)
524 pc_conn_consume(c->
id, n);
527static void raw_send(uint8_t slot,
const void *data,
size_t n)
529 if (!pc_conn_active(slot) || n == 0)
537static void close_conn(uint8_t slot)
542void pc_modbus_rx(uint8_t slot)
549 size_t avail = ring_avail(conn);
556 ring_peek(conn, 0, hdr, 6);
557 uint16_t pid = (uint16_t)((hdr[2] << 8) | hdr[3]);
558 uint16_t len = (uint16_t)((hdr[4] << 8) | hdr[5]);
559 if (pid != 0 || len < 2 || len > (MODBUS_ADU_MAX - 6))
564 size_t frame_total = (size_t)6 + len;
565 if (avail < frame_total)
570 uint8_t adu[MODBUS_ADU_MAX];
571 ring_peek(conn, 0, adu, frame_total);
572 ring_consume(conn, frame_total);
574 uint8_t resp[MODBUS_ADU_MAX];
575 size_t rl = pc_modbus_process_adu(adu, frame_total, resp,
sizeof(resp));
578 raw_send(slot, resp, rl);
586static const ProtoHandler s_modbus_handler = {
nullptr, pc_modbus_rx,
nullptr,
nullptr};
589 return &s_modbus_handler;
Parameterized CRC engine - one source of truth for every cyclic redundancy check.
constexpr pc_crc_params PC_CRC16_MODBUS
CRC-16/MODBUS. check = 0x4B37.
uint32_t pc_crc(const pc_crc_params *p, const uint8_t *data, size_t len)
One-shot CRC of len octets at data.
Zero-heap Modbus TCP slave/server (Modbus Application Protocol v1.1b3).
Layer 5 (Session) - per-protocol connection handler dispatch table.
#define PC_MODBUS_DISCRETE_INPUTS
Number of Modbus discrete inputs (FC 2), single-bit read-only (BSS, bit-packed).
#define PC_MODBUS_COILS
Number of Modbus coils (FC 1/5/15), single-bit R/W (BSS, bit-packed).
#define PC_MODBUS_INPUT_REGS
Number of Modbus input registers (FC 4), 16-bit read-only (BSS).
#define PC_MODBUS_HOLDING_REGS
Number of Modbus holding registers (FC 3/6/16), 16-bit R/W (BSS).
Per-protocol connection event/poll callbacks (Layer 5 dispatch vtable).
A single TCP connection context.
uint8_t id
Fixed slot index (0 … MAX_CONNS-1).
TcpConn conn_pool[CONN_POOL_SLOTS]
Static pool of connection contexts. Defined in tcp.cpp. Sized CONN_POOL_SLOTS: MAX_CONNS TCP slots pl...
void pc_conn_flush(uint8_t slot)
Flush queued bytes / finish the send on slot (TLS-aware).
void pc_conn_close(uint8_t slot)
Close connection slot gracefully (tcp_close), aborting if the FIN cannot be queued....
bool pc_conn_send(uint8_t slot, const void *data, u16_t len)
Send len bytes on connection slot (copies data; TLS-aware).
Layer 4 (Transport) - TCP connection pool, ring buffers, and lwIP integration.