Skip to main content

modbus_impl/
lib.rs

1#![no_std]
2/// RegisterRead:支持 is_valid(addr) 用于越界检查
3pub trait RegisterRead {
4    fn get(&self, addr: u16) -> u16;
5    fn is_valid(&self, addr: u16) -> bool;
6}
7/// 支持 01/02 的位读取(Coil/ISTS)
8pub trait BitRead {
9    fn get(&self, addr: u16) -> bool;
10    fn is_valid(&self, addr: u16) -> bool;
11}
12
13/// Coil 结构体(FC01)
14pub struct Coil<const N: usize> {
15    regs: [bool; N],
16}
17impl<const N: usize> Coil<N> {
18    pub const fn new() -> Self {
19        Self { regs: [false; N] }
20    }
21    pub fn set_bit(&mut self, addr: u16, val: bool) {
22        let i = addr as usize;
23        if i < N {
24            self.regs[i] = val;
25        }
26    }
27    pub fn as_bits(&self) -> &[bool] {
28        &self.regs
29    }
30}
31impl<const N: usize> BitRead for Coil<N> {
32    fn get(&self, addr: u16) -> bool {
33        let i = addr as usize;
34        if i < N {
35            self.regs[i]
36        } else {
37            false
38        }
39    }
40    fn is_valid(&self, addr: u16) -> bool {
41        (addr as usize) < N
42    }
43}
44
45/// ISTS(离散输入)结构体(FC02)
46pub struct Ists<const N: usize> {
47    regs: [bool; N],
48}
49impl<const N: usize> Ists<N> {
50    pub const fn new() -> Self {
51        Self { regs: [false; N] }
52    }
53    pub fn set_bit(&mut self, addr: u16, val: bool) {
54        let i = addr as usize;
55        if i < N {
56            self.regs[i] = val;
57        }
58    }
59}
60impl<const N: usize> BitRead for Ists<N> {
61    fn get(&self, addr: u16) -> bool {
62        let i = addr as usize;
63        if i < N {
64            self.regs[i]
65        } else {
66            false
67        }
68    }
69    fn is_valid(&self, addr: u16) -> bool {
70        (addr as usize) < N
71    }
72}
73
74/// IREG(输入寄存器)结构体(FC04)
75pub struct Ireg<const N: usize> {
76    regs: [u16; N],
77}
78impl<const N: usize> Ireg<N> {
79    pub const fn new() -> Self {
80        Self { regs: [0; N] }
81    }
82    pub fn set(&mut self, addr: u16, val: u16) {
83        let i = addr as usize;
84        if i < N {
85            self.regs[i] = val;
86        }
87    }
88}
89impl<const N: usize> RegisterRead for Ireg<N> {
90    fn get(&self, addr: u16) -> u16 {
91        let i = addr as usize;
92        if i < N {
93            self.regs[i]
94        } else {
95            0
96        }
97    }
98    fn is_valid(&self, addr: u16) -> bool {
99        (addr as usize) < N
100    }
101}
102pub struct Hreg<const N: usize> {
103    regs: [u16; N],
104}
105
106impl<const N: usize> Hreg<N> {
107    pub const fn new() -> Self {
108        Self { regs: [0; N] }
109    }
110
111    pub fn set(&mut self, addr: u16, val: u16) {
112        let i = addr as usize;
113        if i < N {
114            self.regs[i] = val;
115        }
116    }
117
118    pub fn as_slice(&self) -> &[u16] {
119        &self.regs
120    }
121}
122
123// RegisterRead 实现:提供 get + is_valid
124impl<const N: usize> RegisterRead for Hreg<N> {
125    fn get(&self, addr: u16) -> u16 {
126        let i = addr as usize;
127        if i < N {
128            self.regs[i]
129        } else {
130            0
131        }
132    }
133    fn is_valid(&self, addr: u16) -> bool {
134        (addr as usize) < N
135    }
136}
137
138///////////////////////////////////////////////////////////////////////////////
139// 2) random(start_val, end_val)  —— no_std 伪随机
140///////////////////////////////////////////////////////////////////////////////
141
142// xorshift32 seed:单线程场景足够用
143static mut SEED: u32 = 0x1234_5678;
144
145#[inline]
146fn xorshift32_next() -> u32 {
147    // SAFETY: 单线程典型嵌入式用法;如需多核/中断并发请告诉我再改为更安全的方案
148    unsafe {
149        let mut x = SEED;
150        x ^= x << 13;
151        x ^= x >> 17;
152        x ^= x << 5;
153        SEED = x;
154        x
155    }
156}
157
158/// 生成闭区间 [min(start_val,end_val), max(...)] 内的随机 u16
159pub fn random(start_val: u16, end_val: u16) -> u16 {
160    let (lo, hi) = if start_val <= end_val {
161        (start_val, end_val)
162    } else {
163        (end_val, start_val)
164    };
165
166    let span = (hi as u32).wrapping_sub(lo as u32).wrapping_add(1);
167    let r = xorshift32_next() % span;
168    (lo as u32 + r) as u16
169}
170
171#[derive(Clone, Copy, Debug)]
172pub struct Req03 {
173    pub unit_id: u8,
174    pub start_addr: u16,
175    pub quantity: u16,
176}
177
178pub mod exc {
179    pub const ILLEGAL_FUNCTION: u8 = 0x01;
180    pub const ILLEGAL_DATA_ADDRESS: u8 = 0x02;
181    pub const ILLEGAL_DATA_VALUE: u8 = 0x03;
182}
183
184/// Modbus RTU CRC16,用于生成CRC16验证码
185pub fn crc16_modbus(data: &[u8]) -> u16 {
186    let mut crc: u16 = 0xFFFF;
187    for &b in data {
188        crc ^= b as u16;
189        for _ in 0..8 {
190            if (crc & 0x0001) != 0 {
191                crc = (crc >> 1) ^ 0xA001;
192            } else {
193                crc >>= 1;
194            }
195        }
196    }
197    crc
198}
199
200/// 组装 FC01 / FC02 响应:
201/*
202  Unit(1) + Func(1) + ByteCount(1) + PackedBits(N) + CRC(2)
203
204  packed bits:Modbus 规定每字节 bit0 是第一个位(LSB-first)
205*/
206pub fn build_resp_bit_reads<const MAX_QTY: usize, B: BitRead>(
207    out: &mut [u8],
208    unit_id: u8,
209    func: u8, // 0x01 or 0x02
210    start_addr: u16,
211    quantity: u16,
212    bits: &B,
213) -> usize {
214    let qty = quantity as usize;
215    let byte_cnt = (qty + 7) / 8;
216
217    out[0] = unit_id;
218    out[1] = func;
219    out[2] = byte_cnt as u8;
220
221    // 清零位区
222    for i in 0..byte_cnt {
223        out[3 + i] = 0;
224    }
225
226    for j in 0..qty {
227        let addr = start_addr.wrapping_add(j as u16);
228        let bit = bits.get(addr);
229        if bit {
230            let byte_i = j / 8;
231            let bit_i = j % 8;
232            out[3 + byte_i] |= 1u8 << bit_i; // LSB-first
233        }
234    }
235
236    let body_len = 3 + byte_cnt;
237    let crc = crc16_modbus(&out[..body_len]);
238    out[body_len] = (crc & 0xFF) as u8;
239    out[body_len + 1] = (crc >> 8) as u8;
240    body_len + 2
241}
242
243/// 组装 FC04 响应(与 FC03 类似,只是 Func=0x04)
244pub fn build_resp04<const MAX_QTY: usize, R: RegisterRead>(
245    out: &mut [u8],
246    unit_id: u8,
247    start_addr: u16,
248    quantity: u16,
249    regs: &R,
250) -> usize {
251    // 复用 build_resp03,把 func 固定为 0x04
252    let qty = quantity as usize;
253
254    out[0] = unit_id;
255    out[1] = 0x04;
256    out[2] = (qty as u8) * 2;
257
258    for i in 0..qty {
259        let addr = start_addr.wrapping_add(i as u16);
260        let v = regs.get(addr);
261        let base = 3 + i * 2;
262        out[base] = (v >> 8) as u8; // big-endian
263        out[base + 1] = (v & 0xFF) as u8;
264    }
265
266    let body_len = 3 + qty * 2;
267    let crc = crc16_modbus(&out[..body_len]);
268    out[body_len] = (crc & 0xFF) as u8;
269    out[body_len + 1] = (crc >> 8) as u8;
270    body_len + 2
271}
272
273/// 解析固定 8 字节 Modbus RTU 03 请求
274pub fn parse_req03(frame: &[u8; 8]) -> Option<Req03> {
275    if frame[1] != 0x03 {
276        return None;
277    }
278    let expected_crc = u16::from_le_bytes([frame[6], frame[7]]);
279    let calc_crc = crc16_modbus(&frame[..6]);
280    if expected_crc != calc_crc {
281        return None;
282    }
283
284    let start_addr = u16::from_be_bytes([frame[2], frame[3]]);
285    let quantity = u16::from_be_bytes([frame[4], frame[5]]);
286
287    Some(Req03 {
288        unit_id: frame[0],
289        start_addr,
290        quantity,
291    })
292}
293
294/// 组装 03 响应帧:Unit(1)+Func(1)+ByteCount(1)+Reg(2*quantity)+CRC(2)
295pub fn build_resp03<const MAX_QTY: usize, R: RegisterRead>(
296    out: &mut [u8],
297    unit_id: u8,
298    start_addr: u16,
299    quantity: u16,
300    regs: &R,
301) -> usize {
302    let qty = quantity as usize;
303    // 要求:quantity <= MAX_QTY,且 out 足够大
304    out[0] = unit_id;
305    out[1] = 0x03;
306    out[2] = (qty as u8) * 2;
307
308    for i in 0..qty {
309        let addr = start_addr.wrapping_add(i as u16);
310        let v = regs.get(addr);
311        let base = 3 + i * 2;
312        out[base] = (v >> 8) as u8; // register big-endian
313        out[base + 1] = (v & 0xFF) as u8;
314    }
315
316    let body_len = 3 + qty * 2;
317    let crc = crc16_modbus(&out[..body_len]);
318    out[body_len] = (crc & 0xFF) as u8; // CRC low
319    out[body_len + 1] = (crc >> 8) as u8; // CRC high
320    body_len + 2
321}
322
323/// 组装异常响应:Function=03|0x80 + ExceptionCode(1) + CRC(2)
324pub fn build_exception_resp<const BUF: usize>(
325    out: &mut [u8; BUF],
326    unit_id: u8,
327    function_exception: u8, // 例如 0x83
328    exception_code: u8,
329) -> usize {
330    out[0] = unit_id;
331    out[1] = function_exception;
332    out[2] = exception_code;
333
334    let crc = crc16_modbus(&out[..3]);
335    out[3] = (crc & 0xFF) as u8;
336    out[4] = (crc >> 8) as u8;
337    5
338}
339
340///ModbusCtx,用于沟通上下文
341pub struct ModbusCtx<'a, H, I, C, D> {
342    pub holdings: &'a mut H, // FC03
343    pub inputs: &'a mut I,   // FC04
344    pub coils: &'a mut C,    // FC01
345    pub ists: &'a mut D,     // FC02
346}
347
348impl<'a, H, I, C, D> ModbusCtx<'a, H, I, C, D>
349where
350    H: RegisterRead,
351    I: RegisterRead,
352    C: BitRead,
353    D: BitRead,
354{
355    /// pharse_pdu: 解析固定 8 字节 Modbus RTU 请求,并组装响应/异常
356    /// 返回值:
357    /// - 正常响应:返回 out_tx 中的长度
358    /// - 异常响应:写入 out_exc,并返回 5
359    pub fn pharse_pdu<const MAX_QTY: usize>(
360        &self,
361        req8: &[u8; 8],
362        out_tx: &mut [u8],
363        out_exc: &mut [u8; 5],
364    ) -> usize {
365        let unit_id = req8[0];
366        let func = req8[1];
367
368        // -------- CRC check --------
369        let expected_crc = u16::from_le_bytes([req8[6], req8[7]]);
370        let calc_crc = crc16_modbus(&req8[..6]);
371        if expected_crc != calc_crc {
372            // CRC 错:返回 ILLEGAL_DATA_VALUE,功能码用请求 func|0x80
373            return build_exception_resp_fixed::<5>(
374                out_exc,
375                unit_id,
376                func | 0x80,
377                exc::ILLEGAL_DATA_VALUE,
378            );
379        }
380
381        // -------- common fields --------
382        let start_addr = u16::from_be_bytes([req8[2], req8[3]]);
383        let quantity = u16::from_be_bytes([req8[4], req8[5]]);
384
385        // quantity 校验
386        if quantity == 0 || (quantity as usize) > MAX_QTY {
387            return build_exception_resp_fixed::<5>(
388                out_exc,
389                unit_id,
390                func | 0x80,
391                exc::ILLEGAL_DATA_VALUE,
392            );
393        }
394
395        match func {
396            0x01 => {
397                // FC01 Read Coils
398                let end = start_addr.wrapping_add(quantity.saturating_sub(1));
399                if !self.coils.is_valid(start_addr) || !self.coils.is_valid(end) {
400                    return build_exception_resp_fixed::<5>(
401                        out_exc,
402                        unit_id,
403                        func | 0x80,
404                        exc::ILLEGAL_DATA_ADDRESS,
405                    );
406                }
407                build_resp_bit_reads::<MAX_QTY, _>(
408                    out_tx, unit_id, 0x01, start_addr, quantity, self.coils,
409                )
410            }
411
412            0x02 => {
413                // FC02 Read Discrete Inputs
414                let end = start_addr.wrapping_add(quantity.saturating_sub(1));
415                if !self.ists.is_valid(start_addr) || !self.ists.is_valid(end) {
416                    return build_exception_resp_fixed::<5>(
417                        out_exc,
418                        unit_id,
419                        func | 0x80,
420                        exc::ILLEGAL_DATA_ADDRESS,
421                    );
422                }
423                build_resp_bit_reads::<MAX_QTY, _>(
424                    out_tx, unit_id, 0x02, start_addr, quantity, self.ists,
425                )
426            }
427
428            0x03 => {
429                // FC03 Read Holding Registers
430                let end = start_addr.wrapping_add(quantity.saturating_sub(1));
431                if !self.holdings.is_valid(start_addr) || !self.holdings.is_valid(end) {
432                    return build_exception_resp_fixed::<5>(
433                        out_exc,
434                        unit_id,
435                        func | 0x80,
436                        exc::ILLEGAL_DATA_ADDRESS,
437                    );
438                }
439                build_resp_regs::<MAX_QTY, _>(
440                    out_tx,
441                    unit_id,
442                    0x03,
443                    start_addr,
444                    quantity,
445                    self.holdings,
446                )
447            }
448
449            0x04 => {
450                // FC04 Read Input Registers
451                let end = start_addr.wrapping_add(quantity.saturating_sub(1));
452                if !self.inputs.is_valid(start_addr) || !self.inputs.is_valid(end) {
453                    return build_exception_resp_fixed::<5>(
454                        out_exc,
455                        unit_id,
456                        func | 0x80,
457                        exc::ILLEGAL_DATA_ADDRESS,
458                    );
459                }
460                build_resp_regs::<MAX_QTY, _>(
461                    out_tx,
462                    unit_id,
463                    0x04,
464                    start_addr,
465                    quantity,
466                    self.inputs,
467                )
468            }
469
470            _ => {
471                // 不支持功能码
472                build_exception_resp_fixed::<5>(
473                    out_exc,
474                    unit_id,
475                    func | 0x80,
476                    exc::ILLEGAL_FUNCTION,
477                )
478            }
479        }
480    }
481}
482
483// ---------------- helper:异常帧(固定 5 字节) ----------------
484fn build_exception_resp_fixed<const BUF: usize>(
485    out_exc: &mut [u8; 5],
486    unit_id: u8,
487    function_exception: u8,
488    exception_code: u8,
489) -> usize {
490    out_exc[0] = unit_id;
491    out_exc[1] = function_exception;
492    out_exc[2] = exception_code;
493
494    let crc = crc16_modbus(&out_exc[..3]);
495    out_exc[3] = (crc & 0xFF) as u8;
496    out_exc[4] = (crc >> 8) as u8;
497    5
498}
499// ---------------- helper:reg 读取响应(FC03/FC04) ----------------
500// out_tx 需要至少 >= 3 + MAX_QTY*2 + 2
501fn build_resp_regs<const MAX_QTY: usize, R: RegisterRead>(
502    out_tx: &mut [u8],
503    unit_id: u8,
504    func: u8,
505    start_addr: u16,
506    quantity: u16,
507    regs: &R,
508) -> usize {
509    let qty = quantity as usize;
510
511    out_tx[0] = unit_id;
512    out_tx[1] = func;
513    out_tx[2] = (qty as u8) * 2;
514
515    for i in 0..qty {
516        let addr = start_addr.wrapping_add(i as u16);
517        let v = regs.get(addr);
518        let base = 3 + i * 2;
519        out_tx[base] = (v >> 8) as u8;
520        out_tx[base + 1] = (v & 0xFF) as u8;
521    }
522
523    let body_len = 3 + qty * 2;
524    let crc = crc16_modbus(&out_tx[..body_len]);
525    out_tx[body_len] = (crc & 0xFF) as u8;
526    out_tx[body_len + 1] = (crc >> 8) as u8;
527
528    body_len + 2
529}